//
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
//
// Copyright (c) 2009-2010 Math.NET
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using IStation.Numerics.LinearAlgebra.Solvers;
namespace IStation.Numerics.LinearAlgebra.Complex.Solvers
{
using Complex = System.Numerics.Complex;
///
/// This class performs an Incomplete LU factorization with drop tolerance
/// and partial pivoting. The drop tolerance indicates which additional entries
/// will be dropped from the factorized LU matrices.
///
///
/// The ILUTP-Mem algorithm was taken from:
/// ILUTP_Mem: a Space-Efficient Incomplete LU Preconditioner
///
/// Tzu-Yi Chen, Department of Mathematics and Computer Science,
/// Pomona College, Claremont CA 91711, USA
/// Published in:
/// Lecture Notes in Computer Science
/// Volume 3046 / 2004
/// pp. 20 - 28
/// Algorithm is described in Section 2, page 22
///
public sealed class ILUTPPreconditioner : IPreconditioner
{
///
/// The default fill level.
///
public const double DefaultFillLevel = 200.0;
///
/// The default drop tolerance.
///
public const double DefaultDropTolerance = 0.0001;
///
/// The decomposed upper triangular matrix.
///
SparseMatrix _upper;
///
/// The decomposed lower triangular matrix.
///
SparseMatrix _lower;
///
/// The array containing the pivot values.
///
int[] _pivots;
///
/// The fill level.
///
double _fillLevel = DefaultFillLevel;
///
/// The drop tolerance.
///
double _dropTolerance = DefaultDropTolerance;
///
/// The pivot tolerance.
///
double _pivotTolerance;
///
/// Initializes a new instance of the class with the default settings.
///
public ILUTPPreconditioner()
{
}
///
/// Initializes a new instance of the class with the specified settings.
///
///
/// The amount of fill that is allowed in the matrix. The value is a fraction of
/// the number of non-zero entries in the original matrix. Values should be positive.
///
///
/// The absolute drop tolerance which indicates below what absolute value an entry
/// will be dropped from the matrix. A drop tolerance of 0.0 means that no values
/// will be dropped. Values should always be positive.
///
///
/// The pivot tolerance which indicates at what level pivoting will take place. A
/// value of 0.0 means that no pivoting will take place.
///
public ILUTPPreconditioner(double fillLevel, double dropTolerance, double pivotTolerance)
{
if (fillLevel < 0)
{
throw new ArgumentOutOfRangeException(nameof(fillLevel));
}
if (dropTolerance < 0)
{
throw new ArgumentOutOfRangeException(nameof(dropTolerance));
}
if (pivotTolerance < 0)
{
throw new ArgumentOutOfRangeException(nameof(pivotTolerance));
}
_fillLevel = fillLevel;
_dropTolerance = dropTolerance;
_pivotTolerance = pivotTolerance;
}
///
/// Gets or sets the amount of fill that is allowed in the matrix. The
/// value is a fraction of the number of non-zero entries in the original
/// matrix. The standard value is 200.
///
///
///
/// Values should always be positive and can be higher than 1.0. A value lower
/// than 1.0 means that the eventual preconditioner matrix will have fewer
/// non-zero entries as the original matrix. A value higher than 1.0 means that
/// the eventual preconditioner can have more non-zero values than the original
/// matrix.
///
///
/// Note that any changes to the FillLevel after creating the preconditioner
/// will invalidate the created preconditioner and will require a re-initialization of
/// the preconditioner.
///
///
/// Thrown if a negative value is provided.
public double FillLevel
{
get => _fillLevel;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_fillLevel = value;
}
}
///
/// Gets or sets the absolute drop tolerance which indicates below what absolute value
/// an entry will be dropped from the matrix. The standard value is 0.0001.
///
///
///
/// The values should always be positive and can be larger than 1.0. A low value will
/// keep more small numbers in the preconditioner matrix. A high value will remove
/// more small numbers from the preconditioner matrix.
///
///
/// Note that any changes to the DropTolerance after creating the preconditioner
/// will invalidate the created preconditioner and will require a re-initialization of
/// the preconditioner.
///
///
/// Thrown if a negative value is provided.
public double DropTolerance
{
get => _dropTolerance;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_dropTolerance = value;
}
}
///
/// Gets or sets the pivot tolerance which indicates at what level pivoting will
/// take place. The standard value is 0.0 which means pivoting will never take place.
///
///
///
/// The pivot tolerance is used to calculate if pivoting is necessary. Pivoting
/// will take place if any of the values in a row is bigger than the
/// diagonal value of that row divided by the pivot tolerance, i.e. pivoting
/// will take place if row(i,j) > row(i,i) / PivotTolerance for
/// any j that is not equal to i.
///
///
/// Note that any changes to the PivotTolerance after creating the preconditioner
/// will invalidate the created preconditioner and will require a re-initialization of
/// the preconditioner.
///
///
/// Thrown if a negative value is provided.
public double PivotTolerance
{
get => _pivotTolerance;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_pivotTolerance = value;
}
}
///
/// Returns the upper triagonal matrix that was created during the LU decomposition.
///
///
/// This method is used for debugging purposes only and should normally not be used.
///
/// A new matrix containing the upper triagonal elements.
internal Matrix UpperTriangle()
{
return _upper.Clone();
}
///
/// Returns the lower triagonal matrix that was created during the LU decomposition.
///
///
/// This method is used for debugging purposes only and should normally not be used.
///
/// A new matrix containing the lower triagonal elements.
internal Matrix LowerTriangle()
{
return _lower.Clone();
}
///
/// Returns the pivot array. This array is not needed for normal use because
/// the preconditioner will return the solution vector values in the proper order.
///
///
/// This method is used for debugging purposes only and should normally not be used.
///
/// The pivot array.
internal int[] Pivots()
{
var result = new int[_pivots.Length];
for (var i = 0; i < _pivots.Length; i++)
{
result[i] = _pivots[i];
}
return result;
}
///
/// Initializes the preconditioner and loads the internal data structures.
///
///
/// The upon which this preconditioner is based. Note that the
/// method takes a general matrix type. However internally the data is stored
/// as a sparse matrix. Therefore it is not recommended to pass a dense matrix.
///
/// If is .
/// If is not a square matrix.
public void Initialize(Matrix matrix)
{
if (matrix == null)
{
throw new ArgumentNullException(nameof(matrix));
}
if (matrix.RowCount != matrix.ColumnCount)
{
throw new ArgumentException("Matrix must be square.", nameof(matrix));
}
var sparseMatrix = matrix as SparseMatrix ?? SparseMatrix.OfMatrix(matrix);
// The creation of the preconditioner follows the following algorithm.
// spaceLeft = lfilNnz * nnz(A)
// for i = 1, .. , n
// {
// w = a(i,*)
// for j = 1, .. , i - 1
// {
// if (w(j) != 0)
// {
// w(j) = w(j) / a(j,j)
// if (w(j) < dropTol)
// {
// w(j) = 0;
// }
// if (w(j) != 0)
// {
// w = w - w(j) * U(j,*)
// }
// }
// }
//
// for j = i, .. ,n
// {
// if w(j) <= dropTol * ||A(i,*)||
// {
// w(j) = 0
// }
// }
//
// spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row
// lfil = spaceRow / 2 // space for this row of L
// l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements
//
// lfil = spaceRow - nnz(L(i,:)) // space for this row of U
// u(i,j) = w(j) for j = i, .. , n // only the largest lfil - 1 elements
// w = 0
//
// if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary
// {
// pivot by swapping the max and the diagonal entries
// Update L, U
// Update P
// }
// spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:))
// }
// Create the lower triangular matrix
_lower = new SparseMatrix(sparseMatrix.RowCount);
// Create the upper triangular matrix and copy the values
_upper = new SparseMatrix(sparseMatrix.RowCount);
// Create the pivot array
_pivots = new int[sparseMatrix.RowCount];
for (var i = 0; i < _pivots.Length; i++)
{
_pivots[i] = i;
}
var workVector = new DenseVector(sparseMatrix.RowCount);
var rowVector = new DenseVector(sparseMatrix.ColumnCount);
var indexSorting = new int[sparseMatrix.RowCount];
// spaceLeft = lfilNnz * nnz(A)
var spaceLeft = (int) _fillLevel*sparseMatrix.NonZerosCount;
// for i = 1, .. , n
for (var i = 0; i < sparseMatrix.RowCount; i++)
{
// w = a(i,*)
sparseMatrix.Row(i, workVector);
// pivot the row
PivotRow(workVector);
var vectorNorm = workVector.InfinityNorm();
// for j = 1, .. , i - 1)
for (var j = 0; j < i; j++)
{
// if (w(j) != 0)
// {
// w(j) = w(j) / a(j,j)
// if (w(j) < dropTol)
// {
// w(j) = 0;
// }
// if (w(j) != 0)
// {
// w = w - w(j) * U(j,*)
// }
if (workVector[j] != 0.0)
{
// Calculate the multiplication factors that go into the L matrix
workVector[j] = workVector[j]/_upper[j, j];
if (workVector[j].Magnitude < _dropTolerance)
{
workVector[j] = 0.0;
}
// Calculate the addition factor
if (workVector[j] != 0.0)
{
// vector update all in one go
_upper.Row(j, rowVector);
// zero out columnVector[k] because we don't need that
// one anymore for k = 0 to k = j
for (var k = 0; k <= j; k++)
{
rowVector[k] = 0.0;
}
rowVector.Multiply(workVector[j], rowVector);
workVector.Subtract(rowVector, workVector);
}
}
}
// for j = i, .. ,n
for (var j = i; j < sparseMatrix.RowCount; j++)
{
// if w(j) <= dropTol * ||A(i,*)||
// {
// w(j) = 0
// }
if (workVector[j].Magnitude <= _dropTolerance*vectorNorm)
{
workVector[j] = 0.0;
}
}
// spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row
var spaceRow = spaceLeft/(sparseMatrix.RowCount - i + 1);
// lfil = spaceRow / 2 // space for this row of L
var fillLevel = spaceRow/2;
FindLargestItems(0, i - 1, indexSorting, workVector);
// l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements
var lowerNonZeroCount = 0;
var count = 0;
for (var j = 0; j < i; j++)
{
if ((count > fillLevel) || (indexSorting[j] == -1))
{
break;
}
_lower[i, indexSorting[j]] = workVector[indexSorting[j]];
count += 1;
lowerNonZeroCount += 1;
}
FindLargestItems(i + 1, sparseMatrix.RowCount - 1, indexSorting, workVector);
// lfil = spaceRow - nnz(L(i,:)) // space for this row of U
fillLevel = spaceRow - lowerNonZeroCount;
// u(i,j) = w(j) for j = i + 1, .. , n // only the largest lfil - 1 elements
var upperNonZeroCount = 0;
count = 0;
for (var j = 0; j < sparseMatrix.RowCount - i; j++)
{
if ((count > fillLevel - 1) || (indexSorting[j] == -1))
{
break;
}
_upper[i, indexSorting[j]] = workVector[indexSorting[j]];
count += 1;
upperNonZeroCount += 1;
}
// Simply copy the diagonal element. Next step is to see if we pivot
_upper[i, i] = workVector[i];
// if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary
// {
// pivot by swapping the max and the diagonal entries
// Update L, U
// Update P
// }
// Check if we really need to pivot. If (i+1) >=(mCoefficientMatrix.Rows -1) then
// we are working on the last row. That means that there is only one number
// And pivoting is useless. Also the indexSorting array will only contain
// -1 values.
if ((i + 1) < (sparseMatrix.RowCount - 1))
{
if (workVector[i].Magnitude < _pivotTolerance*workVector[indexSorting[0]].Magnitude)
{
// swap columns of u (which holds the values of A in the
// sections that haven't been partitioned yet.
SwapColumns(_upper, i, indexSorting[0]);
// Update P
var temp = _pivots[i];
_pivots[i] = _pivots[indexSorting[0]];
_pivots[indexSorting[0]] = temp;
}
}
// spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:))
spaceLeft -= lowerNonZeroCount + upperNonZeroCount;
}
for (var i = 0; i < _lower.RowCount; i++)
{
_lower[i, i] = 1.0;
}
}
///
/// Pivot elements in the according to internal pivot array
///
/// Row to pivot in
void PivotRow(Vector row)
{
var knownPivots = new Dictionary();
// pivot the row
for (var i = 0; i < row.Count; i++)
{
if ((_pivots[i] != i) && (!PivotMapFound(knownPivots, i)))
{
// store the pivots in the hashtable
knownPivots.Add(_pivots[i], i);
var t = row[i];
row[i] = row[_pivots[i]];
row[_pivots[i]] = t;
}
}
}
///
/// Was pivoting already performed
///
/// Pivots already done
/// Current item to pivot
/// true if performed, otherwise false
bool PivotMapFound(Dictionary knownPivots, int currentItem)
{
if (knownPivots.TryGetValue(_pivots[currentItem], out var knownPivot) && knownPivot.Equals(currentItem))
{
return true;
}
if (knownPivots.TryGetValue(currentItem, out var pivot) && pivot.Equals(_pivots[currentItem]))
{
return true;
}
return false;
}
///
/// Swap columns in the
///
/// Source .
/// First column index to swap
/// Second column index to swap
static void SwapColumns(Matrix matrix, int firstColumn, int secondColumn)
{
for (var i = 0; i < matrix.RowCount; i++)
{
var temp = matrix[i, firstColumn];
matrix[i, firstColumn] = matrix[i, secondColumn];
matrix[i, secondColumn] = temp;
}
}
///
/// Sort vector descending, not changing vector but placing sorted indices to
///
/// Start sort form
/// Sort till upper bound
/// Array with sorted vector indices
/// Source
static void FindLargestItems(int lowerBound, int upperBound, int[] sortedIndices, Vector values)
{
// Copy the indices for the values into the array
for (var i = 0; i < upperBound + 1 - lowerBound; i++)
{
sortedIndices[i] = lowerBound + i;
}
for (var i = upperBound + 1 - lowerBound; i < sortedIndices.Length; i++)
{
sortedIndices[i] = -1;
}
// Sort the first set of items.
// Sorting starts at index 0 because the index array
// starts at zero
// and ends at index upperBound - lowerBound
ILUTPElementSorter.SortDoubleIndicesDecreasing(0, upperBound - lowerBound, sortedIndices, values);
}
///
/// Approximates the solution to the matrix equation Ax = b.
///
/// The right hand side vector.
/// The left hand side vector. Also known as the result vector.
public void Approximate(Vector rhs, Vector lhs)
{
if (_upper == null)
{
throw new ArgumentException("The requested matrix does not exist.");
}
if ((lhs.Count != rhs.Count) || (lhs.Count != _upper.RowCount))
{
throw new ArgumentException("All vectors must have the same dimensionality.", nameof(rhs));
}
// Solve equation here
// Pivot(vector, result);
// Solve L*Y = B(piv,:)
var rowValues = new DenseVector(_lower.RowCount);
for (var i = 0; i < _lower.RowCount; i++)
{
_lower.Row(i, rowValues);
var sum = Complex.Zero;
for (var j = 0; j < i; j++)
{
sum += rowValues[j]*lhs[j];
}
lhs[i] = rhs[i] - sum;
}
// Solve U*X = Y;
for (var i = _upper.RowCount - 1; i > -1; i--)
{
_upper.Row(i, rowValues);
var sum = Complex.Zero;
for (var j = _upper.RowCount - 1; j > i; j--)
{
sum += rowValues[j]*lhs[j];
}
lhs[i] = 1/rowValues[i]*(lhs[i] - sum);
}
// We have a column pivot so we only need to pivot the
// end result not the incoming right hand side vector
var temp = lhs.Clone();
Pivot(temp, lhs);
}
///
/// Pivot elements in according to internal pivot array
///
/// Source .
/// Result after pivoting.
void Pivot(Vector vector, Vector result)
{
for (var i = 0; i < _pivots.Length; i++)
{
result[i] = vector[_pivots[i]];
}
}
}
///
/// An element sort algorithm for the class.
///
///
/// This sort algorithm is used to sort the columns in a sparse matrix based on
/// the value of the element on the diagonal of the matrix.
///
internal static class ILUTPElementSorter
{
///
/// Sorts the elements of the vector in decreasing
/// fashion. The vector itself is not affected.
///
/// The starting index.
/// The stopping index.
/// An array that will contain the sorted indices once the algorithm finishes.
/// The that contains the values that need to be sorted.
public static void SortDoubleIndicesDecreasing(int lowerBound, int upperBound, int[] sortedIndices, Vector values)
{
// Move all the indices that we're interested in to the beginning of the
// array. Ignore the rest of the indices.
if (lowerBound > 0)
{
for (var i = 0; i < (upperBound - lowerBound + 1); i++)
{
Exchange(sortedIndices, i, i + lowerBound);
}
upperBound -= lowerBound;
lowerBound = 0;
}
HeapSortDoublesIndices(lowerBound, upperBound, sortedIndices, values);
}
///
/// Sorts the elements of the vector in decreasing
/// fashion using heap sort algorithm. The vector itself is not affected.
///
/// The starting index.
/// The stopping index.
/// An array that will contain the sorted indices once the algorithm finishes.
/// The that contains the values that need to be sorted.
private static void HeapSortDoublesIndices(int lowerBound, int upperBound, int[] sortedIndices, Vector values)
{
var start = ((upperBound - lowerBound + 1) / 2) - 1 + lowerBound;
var end = (upperBound - lowerBound + 1) - 1 + lowerBound;
BuildDoubleIndexHeap(start, upperBound - lowerBound + 1, sortedIndices, values);
while (end >= lowerBound)
{
Exchange(sortedIndices, end, lowerBound);
SiftDoubleIndices(sortedIndices, values, lowerBound, end);
end -= 1;
}
}
///
/// Build heap for double indices
///
/// Root position
/// Length of
/// Indices of
/// Target
private static void BuildDoubleIndexHeap(int start, int count, int[] sortedIndices, Vector values)
{
while (start >= 0)
{
SiftDoubleIndices(sortedIndices, values, start, count);
start -= 1;
}
}
///
/// Sift double indices
///
/// Indices of
/// Target
/// Root position
/// Length of
private static void SiftDoubleIndices(int[] sortedIndices, Vector values, int begin, int count)
{
var root = begin;
while (root * 2 < count)
{
var child = root * 2;
if ((child < count - 1) && (values[sortedIndices[child]].Magnitude > values[sortedIndices[child + 1]].Magnitude))
{
child += 1;
}
if (values[sortedIndices[root]].Magnitude <= values[sortedIndices[child]].Magnitude)
{
return;
}
Exchange(sortedIndices, root, child);
root = child;
}
}
///
/// Sorts the given integers in a decreasing fashion.
///
/// The values.
public static void SortIntegersDecreasing(int[] values)
{
HeapSortIntegers(values, values.Length);
}
///
/// Sort the given integers in a decreasing fashion using heapsort algorithm
///
/// Array of values to sort
/// Length of
private static void HeapSortIntegers(int[] values, int count)
{
var start = (count / 2) - 1;
var end = count - 1;
BuildHeap(values, start, count);
while (end >= 0)
{
Exchange(values, end, 0);
Sift(values, 0, end);
end -= 1;
}
}
///
/// Build heap
///
/// Target values array
/// Root position
/// Length of
private static void BuildHeap(int[] values, int start, int count)
{
while (start >= 0)
{
Sift(values, start, count);
start -= 1;
}
}
///
/// Sift values
///
/// Target value array
/// Root position
/// Length of
private static void Sift(int[] values, int start, int count)
{
var root = start;
while (root * 2 < count)
{
var child = root * 2;
if ((child < count - 1) && (values[child] > values[child + 1]))
{
child += 1;
}
if (values[root] > values[child])
{
Exchange(values, root, child);
root = child;
}
else
{
return;
}
}
}
///
/// Exchange values in array
///
/// Target values array
/// First value to exchange
/// Second value to exchange
private static void Exchange(int[] values, int first, int second)
{
var t = values[first];
values[first] = values[second];
values[second] = t;
}
}
}