using System;
using System.Collections.Generic;
using System.Linq;
using Deltares.Mathematics;
// ===========================================================================================
// Class name : TMatrix
//
// Description : Class for handling Matrix Routines
// Functions are from Numerical recipies in Pascal,
// Cambridge University Press
//
// Date ID Modification
// ==== == ============
// 2000-02-06 Sel Created
// 2008-06-05 Best Copied from MGeolib
// 2012-06-28 Best class Matrix with CholeskyDecomposition added
// ===========================================================================================
namespace Deltares.Stability.Calculation2.Levenberg
{
public class EDMatrixError : Exception
{
//@ Constructor auto-generated
public EDMatrixError(String message)
: base(message) { }
//@ Constructor auto-generated
public EDMatrixError(String message, Exception innerException)
: base(message, innerException) { }
}
public class TMatrix
{
private int[] FIndex;
private double determinant;
public TMatrix()
{
Tolerance = 0;
}
public int RowCount
{
get
{
return Matrix == null ? 0 : Matrix.Length;
}
}
public int ColumnCount
{
get
{
return RowCount > 0 ? Matrix[0].Length : 0;
}
}
///
/// Data matrix, with first indexer being the row and second being the column.
///
public double[][] Matrix { get; private set; }
///
/// Data vector, of size equal to .
///
/// Contents are independent of .
public double[] Vector { get; private set; }
///
/// Gets or sets the in .
///
///
/// The to be stored in at the
/// given indexes.
///
/// a row number.
/// a column number.
///
/// The stored in at the
/// given indexes, or 0 if indexes are out of bounds.
///
///
/// To speed up the performance for large matrices, use
/// directly rather than this index property.
///
public double this[int aRowNumber, int aColumnNumber]
{
get
{
return aRowNumber >= 0 && aRowNumber < RowCount &&
aColumnNumber >= 0 && aColumnNumber < ColumnCount
? Matrix[aRowNumber][aColumnNumber]
: 0;
}
set
{
if (aRowNumber >= 0 && aRowNumber < RowCount &&
aColumnNumber >= 0 && aColumnNumber < ColumnCount)
{
Matrix[aRowNumber][aColumnNumber] = value;
}
}
}
public double Tolerance { get; set; }
///
/// Clears and resizes and .
///
/// Number of matrix rows and size of vector.
/// Number of matrix columns.
/// When either or
/// is less then zero.
public void SetDimensions(int rowCount, int columnCount)
{
if (rowCount < 0 || columnCount < 0)
{
throw new EDMatrixError("MatrixErrorOutOfRange");
}
Matrix = ArraySupport.CreateJaggedArray(rowCount, columnCount);
Vector = new double[rowCount];
FIndex = new int[rowCount];
}
// ===========================================================================================
// Description : LU_Decomposision of a square matrix, equation (2.3.2),
// Numerical recipies in Pascal.
// The Source may be identical with the Target.
// On output, the Target Matrix is replaced by the Source's
// LU_Decomposision of a rowwise permutation of itself.
// FIndex records the row permutation by the partial pivoting.
// The FDeterminant keeps track of even or odd row interchanges,
// which is important to determine the determinant.
// This routine is used in combination with LU_BackSubst
// to solve linear equations or invert a matrix.
//
// Date ID Modification
// 1997-10-23 Sel Created
// 1998.11.11 Sel Adapted to List of TDMatVector
// 2000.02.06 Sel Zero based index origin applied
// 2000.10.30 Sel Determination Determinant removed
// ===========================================================================================
public void Decomposition(TMatrix source)
{
// For performance:
var rowCount = RowCount;
var columnCount = ColumnCount;
if (rowCount != source.RowCount || columnCount != source.ColumnCount)
{
throw new EDMatrixError("sMatrixErrorNoEqualSize");
}
if (rowCount != columnCount)
{
throw new EDMatrixError("sMatrixErrorNotSquared");
}
// stores the implicit scaling of each row
var rowNumber = -1;
double largeValue = 0;
var scaling = new double[rowCount];
// define the Scaling
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
largeValue = 0;
// Copy the source on the way
Vector[rowIndex] = source.Vector[rowIndex];
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
Matrix[rowIndex][columnIndex] = source.Matrix[rowIndex][columnIndex];
largeValue = Math.Max(largeValue, Math.Abs(source.Matrix[rowIndex][columnIndex]));
}
if (largeValue == 0)
{
throw new EDMatrixError("sMatrixErrorSingular");
}
else
{
scaling[rowIndex] = 1 / largeValue;
}
}
// execute Crouts method
var tolerance = Tolerance * largeValue;
determinant = 1.0;
// No row interchanges yet
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
// Equation (2.3.12) except for rowIndex = LColumnIndex
double croutSum;
for (int rowIndex = 0; rowIndex < columnIndex; rowIndex++)
{
croutSum = Matrix[rowIndex][columnIndex];
for (int commonIndex = 0; commonIndex < rowIndex; commonIndex++)
{
croutSum -= Matrix[rowIndex][commonIndex] * Matrix[commonIndex][columnIndex];
}
Matrix[rowIndex][columnIndex] = croutSum;
}
// rowIndex = LColumnIndex of equation (2.3.12) and equation (2.3.13)
largeValue = tolerance;
//double LDummy;
for (int rowIndex = columnIndex; rowIndex < rowCount; rowIndex++)
{
croutSum = Matrix[rowIndex][columnIndex];
for (int commonIndex = 0; commonIndex < columnIndex; commonIndex++)
{
croutSum -= Matrix[rowIndex][commonIndex] * Matrix[commonIndex][columnIndex];
}
Matrix[rowIndex][columnIndex] = croutSum;
// Is the figure of merit for the pivot better than the best so far?
var dummy = scaling[rowIndex] * Math.Abs(croutSum);
if (dummy >= largeValue)
{
largeValue = dummy;
rowNumber = rowIndex;
}
}
if (largeValue == tolerance)
{
throw new EDMatrixError("sMatrixErrorSingular");
}
// Do we need to interchange rows?
if (columnIndex != rowNumber)
{
for (int commonIndex = 0; commonIndex < columnCount; commonIndex++)
{
var swapValue = Matrix[rowNumber][commonIndex];
Matrix[rowNumber][commonIndex] = Matrix[columnIndex][commonIndex];
Matrix[columnIndex][commonIndex] = swapValue;
}
// Change the parity of FDeterminant and interchange the Scaling factor
determinant = -determinant;
scaling[rowNumber] = scaling[columnIndex];
}
// Now, finally, divide by the pivot element
FIndex[columnIndex] = rowNumber;
if (columnIndex != columnCount)
{
var pivotFactor = 1 / Matrix[columnIndex][columnIndex];
for (int rowIndex = columnIndex + 1; rowIndex < rowCount; rowIndex++)
{
Matrix[rowIndex][columnIndex] = Matrix[rowIndex][columnIndex] * pivotFactor;
}
}
}
// Determine the determinant value
for (int diagonalIndex = 0; diagonalIndex < rowCount; diagonalIndex++)
{
determinant = determinant * Matrix[diagonalIndex][diagonalIndex];
}
}
// ===========================================================================================
// Description : The solution vector FVector[1. .RowCount] is improved, see
// equations (2.7.1) ...(2.7.4). The orignal matrix Source.Matrix
// and vector Source.Vector are applied.
//
// Date ID Modification
// 1997-10-27 Sel Created
// 1998.11.11 Sel Adapted to List of TDMatVector
// 2000.02.06 Sel Zero based inxex origin applied
// ===========================================================================================
public void ImprovedSolver(TMatrix ASource)
{
// Check if Source is different from the matrix
if (this == ASource)
{
throw new EDMatrixError("sMatrixErrorNoSource");
}
// Check if the matrix is square
if (RowCount != ColumnCount)
{
throw new EDMatrixError("sMatrixErrorNotSquared");
}
BackSubstitution(Vector);
var LRightHand = new double[RowCount];
// Calculate the right-handside, accumulating the residual
for (var LRowIndex = 0; LRowIndex < RowCount; LRowIndex++)
{
var LSolverSum = -ASource.Vector[LRowIndex];
for (var LColumnIndex = 0; LColumnIndex < ColumnCount; LColumnIndex++)
{
//@ Unsupported property or method(A): 'Matrix'
LSolverSum = LSolverSum + ASource.Matrix[LRowIndex][LColumnIndex] * Vector[LColumnIndex];
}
LRightHand[LRowIndex] = LSolverSum;
}
// Solve for the error term, and subtract it from the old solution
BackSubstitution(LRightHand);
for (var LRowIndex = 0; LRowIndex < RowCount; LRowIndex++)
{
Vector[LRowIndex] = Vector[LRowIndex] - LRightHand[LRowIndex];
}
}
// ===========================================================================================
// Description : Solves the set of RowCount linear equations A.X = B. Here
// FMatrix[l..RowCount,l..RowCount] is input, not as the matrix A
// but rather as its LU decomposition, determined by the routine
// Decomposition. FIndex[l..RowCount] is input as the permutation
// vector returned by Decomposition. FVector[l..RowCount] is input
// as the right-hand side vector B, and returns with the solution
// vector X. FMatrix, RowCount, and FIndex are not modified by
// this routine and can be left in place for successive calls
// with different right-hand sides FVector. This routine takes
// into account the possibility that FVector will begin with many
// zero elements, so it is efficient for use in matrix inversion.
//
// Date ID Modification
// 1997-10-27 Sel Created
// 1998.11.11 Sel Adapted to List of TDMatVector
// 2000.02.07 Sel Zero based inxex origin applied
// ===========================================================================================
///
/// Multiplies this with another matrix and vector.
///
/// The other matrix and vector.
/// A new matrix and vector.
public TMatrix Multiply(TMatrix other)
{
if (ColumnCount != other.RowCount)
{
throw new EDMatrixError("sMatrixErrorNoEqualSize");
}
var result = new TMatrix();
result.SetDimensions(RowCount, other.ColumnCount);
for (var rowIndex = 0; rowIndex < RowCount; rowIndex++)
{
for (var commonIndex = 0; commonIndex < ColumnCount; commonIndex++)
{
var matrixValue = Matrix[rowIndex][commonIndex];
for (var otherColumnIndex = 0; otherColumnIndex < other.ColumnCount; otherColumnIndex++)
{
result.Matrix[rowIndex][otherColumnIndex] += matrixValue * other.Matrix[commonIndex][otherColumnIndex];
}
result.Vector[rowIndex] += matrixValue * other.Vector[commonIndex];
}
}
return result;
}
///
/// Product of Transposed Matrix and Matrix self
///
public TMatrix Quadrature()
{
return Transpose().Multiply(this);
}
///
/// Performs a matrix addition.
///
/// Another matrix.
/// When is not of the same
/// size as this.
/// This method causes no change to .
public void Add(TMatrix other)
{
// For performance:
var rowCount = RowCount;
var columnCount = ColumnCount;
if (rowCount != other.RowCount || columnCount != other.ColumnCount)
{
throw new EDMatrixError("sMatrixErrorNoEqualSize");
}
for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
Matrix[rowIndex][columnIndex] = Matrix[rowIndex][columnIndex] + other.Matrix[rowIndex][columnIndex];
}
}
}
///
/// Multiply all elements by a factor.
///
/// The multiplication factor.
public void MultiplyByFactor(double factor)
{
// For performance:
var rowCount = RowCount;
var columnCount = ColumnCount;
for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
Matrix[rowIndex][columnIndex] = factor * Matrix[rowIndex][columnIndex];
}
}
}
///
/// Returns the transpose of .
///
public TMatrix Transpose()
{
// For performance
var columnCount = ColumnCount;
var rowCount = RowCount;
var result = new TMatrix();
result.SetDimensions(columnCount, rowCount);
for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
result.Matrix[columnIndex][rowIndex] = Matrix[rowIndex][columnIndex];
}
}
return result;
}
///
/// Creates a clone of and .
///
///
public TMatrix Duplicate()
{
// For performance:
var rowCount = RowCount;
var columnCount = ColumnCount;
var result = new TMatrix();
result.SetDimensions(rowCount, columnCount);
for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
result.Matrix[rowIndex][columnIndex] = Matrix[rowIndex][columnIndex];
}
result.Vector[rowIndex] = Vector[rowIndex];
}
return result;
}
// ===========================================================================================
// Description : Initialize Matrix cells
//
// Date ID Modification
// 2000.02.12 Sel Created
// 2000.03.21 Sel UserAbort and ProgressEvent added
// ===========================================================================================
public void SetToNaught()
{
// For performance:
var rowCount = RowCount;
var columnCount = ColumnCount;
for (var rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
for (var columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
Matrix[rowIndex][columnIndex] = 0;
}
Vector[rowIndex] = 0;
}
}
///
/// Calculated the matrix determinant.
///
/// When this is not a square matrix or is singular.
public double FetchDeterminant()
{
Decomposition(this);
return determinant;
}
///
/// Creates a new matrix which doesn't contain completely empty rows or columns
///
///
///
public TMatrix GetFilteredMatrix(out int[] excluded)
{
int count = Math.Min(RowCount, ColumnCount);
var excludedRows = new List();
// Detect rows and columns which only have zeroes
for (int i = 0; i < count; i++)
{
bool allZeroRow = true;
bool allZeroColumn = true;
for (int j = 0; j < count; j++)
{
if (this[i, j] != 0)
{
allZeroRow = false;
}
if (this[j, i] != 0)
{
allZeroColumn = false;
}
}
if (allZeroRow || allZeroColumn)
{
excludedRows.Add(i);
}
}
// Nothing to exclude, return original matrix
if (excludedRows.Count == 0)
{
excluded = new int[0];
return this;
}
// Build up new matrix without zero elements
var newMatrix = new TMatrix();
int size = count - excludedRows.Count;
newMatrix.SetDimensions(size, size);
int skippedRows = 0;
for (int i = 0; i < count; i++)
{
if (excludedRows.Contains(i))
{
skippedRows++;
}
else
{
newMatrix.Vector[i - skippedRows] = Vector[i];
int skippedColumns = 0;
for (int j = 0; j < count; j++)
{
if (excludedRows.Contains(j))
{
skippedColumns++;
}
else
{
newMatrix[i - skippedRows, j - skippedColumns] = this[i, j];
}
}
}
}
excluded = excludedRows.ToArray();
return newMatrix;
}
public bool IsRowAllZero(int rowIndex)
{
for (int i = 0; i < ColumnCount; i++)
{
if (this[rowIndex, i] != 0)
{
return false;
}
}
return true;
}
public bool IsColumnAllZero(int columnIndex)
{
for (int i = 0; i < RowCount; i++)
{
if (this[i, columnIndex] != 0)
{
return false;
}
}
return true;
}
///
/// Combines the current vector with a given vector
///
///
///
public void CombineVector(TMatrix matrix, int[] excludedRows)
{
int[] mapping = new int[Vector.Length];
for (int i = 0; i < Vector.Length; i++)
{
int skipped = 0;
for (int k = 0; k < excludedRows.Length; k++)
{
if (excludedRows[k] == i)
{
mapping[i] = -1;
}
else if (excludedRows[k] < i)
{
skipped++;
}
}
if (mapping[i] != -1)
{
mapping[i] = i - skipped;
}
}
for (int i = 0; i < Vector.Length; i++)
{
if (mapping[i] != -1)
{
Vector[i] = matrix.Vector[mapping[i]];
for (int j = 0; j < matrix.ColumnCount; j++)
{
if (mapping[j] != -1)
{
this[i, j] = matrix[mapping[i], mapping[j]];
}
}
}
}
}
private void BackSubstitution(double[] AVector)
{
double LSolverSum;
var LCellNumber = -1;
// When CellNumber is set to a non-negative value it will become the index of the first
// nonvanishing element of Vector. We now do the forward substitution, equation
// (2.3.6). The only new wrinkle is to unscramhle the permutation as we go.
for (var LRowIndex = 0; LRowIndex < RowCount; LRowIndex++)
{
var LRowNumber = FIndex[LRowIndex];
LSolverSum = AVector[LRowNumber];
AVector[LRowNumber] = AVector[LRowIndex];
if (LCellNumber != -1)
{
for (var LCommonIndex = LCellNumber; LCommonIndex < LRowIndex; LCommonIndex++)
{
LSolverSum = LSolverSum - Matrix[LRowIndex][LCommonIndex] * AVector[LCommonIndex];
}
}
else
{
// When a nonzero element is encountered the loop above is entered
if (LSolverSum != 0)
{
LCellNumber = LRowIndex;
}
}
AVector[LRowIndex] = LSolverSum;
}
// Now we do the backsubstitution, equation (2.3.7)
for (var LRowIndex = RowCount - 1; LRowIndex >= 0; LRowIndex--)
{
LSolverSum = AVector[LRowIndex];
for (var LCommonIndex = LRowIndex + 1; LCommonIndex < RowCount; LCommonIndex++)
{
LSolverSum = LSolverSum - Matrix[LRowIndex][LCommonIndex] * AVector[LCommonIndex];
}
// Store a component of the solution vector
AVector[LRowIndex] = LSolverSum / Matrix[LRowIndex][LRowIndex];
}
}
}
}