// // 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; } } }