From 1a8cce73c63cbcfe54148c20f0041913337f5b3c Mon Sep 17 00:00:00 2001 From: Marcus Cuda Date: Sun, 8 Aug 2010 19:54:06 +0800 Subject: [PATCH] Merged Andriy's additions as follows: Added optimized versions of L1, Frobenius, and infinity norms to the dense and sparse matrices Added a TransposeAndMultiply method to Matrix DenseMatrix, and SparseMatrix Ported the Gram-Schmidt QR factorization from dnAnalytics Ported the diagonal matrix from dnAnalytics Ported the iterative solvers from dnAnalytics --- src/MathNet.Numerics.5.0.ReSharper | 745 +------- .../ManagedLinearAlgebraProvider.cs | 6 +- src/Numerics/Constants.cs | 3 + .../LinearAlgebra/Double/DenseMatrix.cs | 124 ++ .../LinearAlgebra/Double/DiagonalMatrix.cs | 1552 +++++++++++++++++ .../Double/Factorization/DenseQR.cs | 1 + .../Double/Factorization/ExtensionMethods.cs | 11 + .../Double/Factorization/GramSchmidt.cs | 284 +++ .../Double/Factorization/SparseCholesky.cs | 223 +++ .../Double/Factorization/SparseLU.cs | 300 ++++ .../Double/Factorization/SparseQR.cs | 332 ++++ .../Double/Factorization/SparseSvd.cs | 923 ++++++++++ .../Double/Factorization/UserQR.cs | 7 +- .../LinearAlgebra/Double/Matrix.Arithmetic.cs | 113 +- .../Double/Solvers/IIterativeSolver.cs | 96 + .../Double/Solvers/IIterativeSolverSetup.cs | 71 + .../LinearAlgebra/Double/Solvers/IIterator.cs | 108 ++ .../Double/Solvers/Iterative/BiCgStab.cs | 524 ++++++ .../Solvers/Iterative/CompositeSolver.cs | 627 +++++++ .../Double/Solvers/Iterative/GpBiCg.cs | 634 +++++++ .../Double/Solvers/Iterative/MlkBiCgStab.cs | 788 +++++++++ .../Double/Solvers/Iterative/TFQMR.cs | 532 ++++++ .../LinearAlgebra/Double/Solvers/Iterator.cs | 325 ++++ .../Solvers/Preconditioners/Diagonal.cs | 148 ++ .../Preconditioners/IPreConditioner.cs | 73 + .../Double/Solvers/Preconditioners/Ilutp.cs | 727 ++++++++ .../Preconditioners/IlutpElementSorter.cs | 225 +++ .../Solvers/Preconditioners/IncompleteLU.cs | 258 +++ .../Preconditioners/UnitPreconditioner.cs | 136 ++ .../Solvers/Status/CalculationCancelled.cs | 49 + .../Solvers/Status/CalculationConverged.cs | 51 + .../Solvers/Status/CalculationDiverged.cs | 51 + .../Solvers/Status/CalculationFailure.cs | 51 + .../Solvers/Status/CalculationIndetermined.cs | 49 + .../Solvers/Status/CalculationRunning.cs | 51 + .../CalculationStoppedWithoutConvergence.cs | 52 + .../Solvers/Status/ICalculationStatus.cs | 43 + .../StopCriterium/DivergenceStopCriterium.cs | 391 +++++ .../StopCriterium/FailureStopCriterium.cs | 199 +++ .../StopCriterium/IIterationStopCriterium.cs | 79 + .../IterationCountStopCriterium.cs | 225 +++ .../StopCriterium/ResidualStopCriterium.cs | 407 +++++ .../Double/Solvers/StopCriterium/StopLevel.cs | 57 + .../LinearAlgebra/Double/SparseMatrix.cs | 214 ++- .../LinearAlgebra/Double/SparseVector.cs | 22 +- src/Numerics/Numerics.csproj | 69 +- src/Numerics/Precision.cs | 22 + src/Numerics/Properties/Resources.Designer.cs | 18 + src/Numerics/Properties/Resources.resx | 6 + src/Numerics/Threading/CommonParallel.cs | 62 + src/Silverlight/Silverlight.csproj | 105 ++ src/UnitTests/AssertHelpers.cs | 38 +- .../Double/DenseMatrixTests.cs | 30 +- .../Double/DiagonalMatrixTests.cs | 449 +++++ .../Double/Factorization/GramSchmidtTests.cs | 330 ++++ .../Double/LinearAlgebraProviderTests.cs | 8 +- .../LinearAlgebraTests/Double/MatrixLoader.cs | 54 +- .../Double/MatrixTests.Arithmetic.cs | 218 ++- .../LinearAlgebraTests/Double/MatrixTests.cs | 264 +-- .../Double/Solvers/Iterative/BiCgStabTest.cs | 205 +++ .../Double/Solvers/Iterative/GpBiCgTest.cs | 207 +++ .../Solvers/Iterative/MlkBiCgStabTest.cs | 205 +++ .../Double/Solvers/Iterative/TFQMRTest.cs | 204 +++ .../Double/Solvers/IteratorTest.cs | 353 ++++ .../Solvers/Preconditioners/DiagonalTest.cs | 30 + .../Preconditioners/IluptElementSorterTest.cs | 502 ++++++ .../Solvers/Preconditioners/IlutpTest.cs | 248 +++ .../Preconditioners/IncompleteLUTest.cs | 82 + .../Preconditioners/PreConditionerTest.cs | 126 ++ .../Preconditioners/UnitPreconditionerTest.cs | 27 + .../DivergenceStopCriteriumTest.cs | 233 +++ .../StopCriterium/FailureStopCriteriumTest.cs | 133 ++ .../IterationCountStopCriteriumTest.cs | 96 + .../ResidualStopCriteriumTest.cs | 261 +++ .../Double/SparseMatrixTests.cs | 16 +- src/UnitTests/UnitTests.csproj | 17 + 76 files changed, 15427 insertions(+), 1078 deletions(-) create mode 100644 src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Factorization/SparseLU.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Factorization/SparseQR.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Factorization/SparseSvd.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolver.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolverSetup.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/IIterator.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Iterative/BiCgStab.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Iterative/CompositeSolver.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Iterative/GpBiCg.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Iterative/TFQMR.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Iterator.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Diagonal.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IPreConditioner.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Ilutp.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IlutpElementSorter.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IncompleteLU.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/UnitPreconditioner.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationCancelled.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationConverged.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationDiverged.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationFailure.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationIndetermined.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationRunning.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationStoppedWithoutConvergence.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/Status/ICalculationStatus.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/DivergenceStopCriterium.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/FailureStopCriterium.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IIterationStopCriterium.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IterationCountStopCriterium.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/ResidualStopCriterium.cs create mode 100644 src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/StopLevel.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/DiagonalMatrixTests.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/BiCgStabTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/GpBiCgTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/MlkBiCgStabTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/TFQMRTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/IteratorTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/DiagonalTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IluptElementSorterTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IlutpTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IncompleteLUTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/PreConditionerTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/UnitPreconditionerTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/DivergenceStopCriteriumTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/FailureStopCriteriumTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/IterationCountStopCriteriumTest.cs create mode 100644 src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/ResidualStopCriteriumTest.cs diff --git a/src/MathNet.Numerics.5.0.ReSharper b/src/MathNet.Numerics.5.0.ReSharper index bbe461a5..bce082e6 100644 --- a/src/MathNet.Numerics.5.0.ReSharper +++ b/src/MathNet.Numerics.5.0.ReSharper @@ -3,750 +3,6 @@ SOLUTION - - - - - - en-US - false - pointwise -Frobenius -indices -ipiv -blocksize -Dont -Cholesky -Matlab -Endian - - - - en-US - en-US - en-US - - en-US - - - Public Protected ProtectedInternal - Any - Maybe - Maybe - Any - - - false - 80 - - - - - false - - - Any - Class - Maybe - Maybe - Any - - - Any - Enum - - Maybe - Maybe - Any - - - Any - EnumerationMember - - Maybe - Maybe - Any - - - - Classes, Enumerations, Enumeration values should be named in Pascal - - - _ - m_ - - Pascal - - - - - false - - - Public Internal Protected ProtectedInternal - Property - - Maybe - Maybe - Any - - - Public Internal Protected ProtectedInternal - Method - - Maybe - Maybe - Any - - - Public Internal Protected ProtectedInternal - Event - - Maybe - Maybe - Any - - - - Non private properties, methods, events should be in pascal. - - - _ - m_ - - Pascal - - - - - true - - - Private - Property - - Maybe - Maybe - Any - - - Private - Method - - Maybe - Maybe - Any - - - Private - Event - - Maybe - Maybe - Any - - - - Private properties, methods, events should be in camel. - - - _ - m_ - - Camel - - - - - false - - - Any - Field - - Maybe - Maybe - Any - - - - Fields should be underscore camel. - - _ - - - m_ - - Camel - - - - - false - - - Any - Variable - Maybe - Maybe - Any - - - Variable should be declared in camel. - - - _ - m_ - - Camel - - - - - false - - - Any - Parameter - Maybe - Maybe - Any - - - Function parameters should be in camel. - - - _ - m_ - - Camel - - - - - false - - - Any - Namespace - Maybe - Maybe - Any - - - Namespaces should be in Pascal. - - - _ - m_ - - Pascal - - - - - false - - - Any - Constant - Maybe - Maybe - Any - - - Constants should be in capital. - - - UpperCase - - - - - false - - - Any - Any - Maybe - Maybe - Any - - - - - Any - Constant - Maybe - Maybe - Any - - - Acronyms should not have more than 2 characters. - - - MatchesRegex - - - ^(?>(XML|SQL|[A-Z]{0,2})[A-Z]?([^A-Z]|$)|[^A-Z]+)*$ - - - false - - - Any - Any - Maybe - Maybe - Any - - - - - Any - Constant - - Maybe - Maybe - Any - - - Any - Field - - Maybe - Maybe - Any - - - Private - Method - - Maybe - Maybe - Any - - - None - Namespace - Maybe - Maybe - Any - - - Names should not have underscore character - - - NotMatchesRegex - - - (?<remove>_) - - - false - - - Any - Class - NUnit.Framework.TestFixtureAttribute - Maybe - Maybe - Any - - - TestFixtures should end with Test. - - - Test - - None - - Test - - - - - false - - - Any - Method - NUnit.Framework.TestAttribute - Maybe - Maybe - Any - - - Test methods should start with Test. - - Test - - - None - - - Test - - - - false - - - Any - Enum - Maybe - Maybe - Any - - - Enumerations should not end with Enum. - - - None - - - Enum - - - - false - - - Any - EnumerationMember - Maybe - Maybe - Any - - - Do not name enumerations reserved. - - - NotMatchesRegex - - - (?<remove>(reserved|Reserved)) - - - false - - - Any - Event - Maybe - Maybe - Any - - - Event should not have Before or After prefix. - - - Before - After - - None - - - - - false - - - Any - Enum - System.FlagsAttribute - Maybe - Maybe - Any - - - Flags enums should have plural names - - - None - - s - - - - - true - - - Any - Enum - Maybe - Maybe - Any - - - - - Any - Enum - System.FlagsAttribute - Maybe - Maybe - Any - - - Enums that are not flags should not have plural names - - - None - - - s - - - - false - - - Any - Class - System.Attribute - Maybe - Maybe - Any - - - Attribute should end with Attribute. - - - None - - Attribute - - - - - false - - - Any - Class - System.EventArgs - Maybe - Maybe - Any - - - EventArgs should end with EventArgs. - - - None - - EventArgs - - - - - false - - - Any - Class - System.Exception - Maybe - Maybe - Any - - - Exceptions should end with Exception. - - - None - - Exception - - - - - false - - - Any - Class - System.Collections.Stack - Maybe - Maybe - Any - - - Any - Class - System.Collections.Generic.Stack - - Maybe - Maybe - Any - - - - Stack should end with Collection or Stack. - - - None - - Collection - Stack - - - - - false - - - Any - Class - System.Collections.ICollection - Maybe - Maybe - Any - - - Any - Class - System.Collections.IEnumerable - Maybe - Maybe - Any - - - Any - Class - System.Collections.Generic.ICollection - Maybe - Maybe - Any - - - - - Any - Class - System.Collections.Stack - Maybe - Maybe - Any - - - Any - Class - System.Collections.Queue - Maybe - Maybe - Any - - - Any - Class - System.Collections.Generic.Stack - - Maybe - Maybe - Any - - - Collections should end with Collection. - - - None - - Collection - - - - - false - - - Any - Class - System.Collections.IDictionary - Maybe - Maybe - Any - - - Any - Class - System.Collections.Generic.IDictionary - Maybe - Maybe - Any - - - Dictionary should end with Dictionary. - - - None - - Dictionary - - - - - false - - - Any - Class - System.Collections.Queue - Maybe - Maybe - Any - - - Queue should end with Collection or Queue. - - - None - - Collection - Queue - - - - - false - - - Any - Interface - Maybe - Maybe - Any - - - Interfaces should be in Pascal and start with I. - - I - - - Pascal - - - - - - - - - Public Protected ProtectedInternal - Any - Maybe - Maybe - Any - - - - (?#email)\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* - (?#url)http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)? - - - NEXT_LINE @@ -866,6 +122,7 @@ Endian + diff --git a/src/Numerics/Algorithms/LinearAlgebra/ManagedLinearAlgebraProvider.cs b/src/Numerics/Algorithms/LinearAlgebra/ManagedLinearAlgebraProvider.cs index d7e3d74d..3c10d0f1 100644 --- a/src/Numerics/Algorithms/LinearAlgebra/ManagedLinearAlgebraProvider.cs +++ b/src/Numerics/Algorithms/LinearAlgebra/ManagedLinearAlgebraProvider.cs @@ -3650,7 +3650,7 @@ namespace MathNet.Numerics.Algorithms.LinearAlgebra // TODO - For small matrices we should get rid of the parallelism because of startup costs. // Perhaps the following implementations would be a good one // http://blog.feradz.com/2009/01/cache-efficient-matrix-multiplication/ - MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0f, x, xRows, xColumns, y, yRows, yColumns, 0.0f, result); + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 1.0f, xdata, xRows, xColumns, ydata, yRows, yColumns, 0.0f, result); } /// @@ -4635,7 +4635,7 @@ namespace MathNet.Numerics.Algorithms.LinearAlgebra // TODO - For small matrices we should get rid of the parallelism because of startup costs. // Perhaps the following implementations would be a good one // http://blog.feradz.com/2009/01/cache-efficient-matrix-multiplication/ - MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex.One, x, xRows, xColumns, y, yRows, yColumns, Complex.Zero, result); + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex.One, xdata, xRows, xColumns, ydata, yRows, yColumns, Complex.Zero, result); } /// @@ -5585,7 +5585,7 @@ namespace MathNet.Numerics.Algorithms.LinearAlgebra // TODO - For small matrices we should get rid of the parallelism because of startup costs. // Perhaps the following implementations would be a good one // http://blog.feradz.com/2009/01/cache-efficient-matrix-multiplication/ - MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex32.One, x, xRows, xColumns, y, yRows, yColumns, Complex32.Zero, result); + MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, Complex32.One, xdata, xRows, xColumns, ydata, yRows, yColumns, Complex32.Zero, result); } /// diff --git a/src/Numerics/Constants.cs b/src/Numerics/Constants.cs index 51d7216d..513d20ea 100644 --- a/src/Numerics/Constants.cs +++ b/src/Numerics/Constants.cs @@ -30,6 +30,9 @@ namespace MathNet.Numerics { + using System.Numerics; + using System.Runtime.InteropServices; + /// /// A collection of frequently used mathematical constants. /// diff --git a/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs b/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs index 7e6e1829..6ad37f3d 100644 --- a/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs +++ b/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs @@ -215,6 +215,61 @@ namespace MathNet.Numerics.LinearAlgebra.Double return ret; } + /// Calculates the L1 norm. + /// The L1 norm of the matrix. + public override double L1Norm() + { + var norm = 0.0; + for (var j = 0; j < ColumnCount; j++) + { + var s = 0.0; + for (var i = 0; i < RowCount; i++) + { + s += Math.Abs(Data[(j * RowCount) + i]); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// Calculates the Frobenius norm of this matrix. + /// The Frobenius norm of this matrix. + public override double FrobeniusNorm() + { + var transpose = (DenseMatrix)Transpose(); + var aat = this * transpose; + + var norm = 0.0; + for (var i = 0; i < RowCount; i++) + { + norm += Math.Abs(aat.Data[(i * RowCount) + i]); + } + + norm = Math.Sqrt(norm); + return norm; + } + + /// Calculates the infinity norm of this matrix. + /// The infinity norm of this matrix. + public override double InfinityNorm() + { + var norm = 0.0; + for (var i = 0; i < RowCount; i++) + { + var s = 0.0; + for (var j = 0; j < ColumnCount; j++) + { + s += Math.Abs(Data[(j * RowCount) + i]); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + #region Elementary operations /// @@ -387,6 +442,75 @@ namespace MathNet.Numerics.LinearAlgebra.Double return result; } + /// + /// Multiplies this dense matrix with transpose of another dense matrix and places the results into the result dense matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If the other matrix is . + /// If the result matrix is . + /// If this.Columns != other.Rows. + /// If the result matrix's dimensions are not the this.Rows x other.Columns. + public override void TransposeAndMultiply(Matrix other, Matrix result) + { + var otherDense = other as DenseMatrix; + var resultDense = result as DenseMatrix; + + if (otherDense == null || resultDense == null) + { + base.TransposeAndMultiply(other, result); + return; + } + + if (ColumnCount != otherDense.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if ((resultDense.RowCount != RowCount) || (resultDense.ColumnCount != otherDense.RowCount)) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + Control.LinearAlgebraProvider.MatrixMultiplyWithUpdate( + Algorithms.LinearAlgebra.Transpose.DontTranspose, + Algorithms.LinearAlgebra.Transpose.Transpose, + 1.0, + Data, + RowCount, + ColumnCount, + otherDense.Data, + otherDense.RowCount, + otherDense.ColumnCount, + 1.0, + resultDense.Data); + } + + /// + /// Multiplies this matrix with transpose of another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.Rows. + /// If the other matrix is . + /// The result of multiplication. + public override Matrix TransposeAndMultiply(Matrix other) + { + var otherDense = other as DenseMatrix; + if (otherDense == null) + { + return base.TransposeAndMultiply(other); + } + + if (ColumnCount != otherDense.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var result = (DenseMatrix)CreateMatrix(RowCount, other.RowCount); + TransposeAndMultiply(other, result); + return result; + } + /// /// Multiplies two dense matrices. /// diff --git a/src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs b/src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs new file mode 100644 index 00000000..4b558b04 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs @@ -0,0 +1,1552 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double +{ + using System; + using System.Linq; + using Properties; + using Threading; + + /// + /// A matrix type for diagonal matrices. + /// + /// + /// Diagonal matrices can be non-square matrices but the diagonal always starts + /// at element 0,0. A diagonal matrix will throw an exception if non diagonal + /// entries are set. The exception to this is when the off diagonal elements are + /// 0.0 or NaN; these settings will cause no change to the diagonal matrix. + /// + public class DiagonalMatrix : Matrix + { + /// + /// Initializes a new instance of the class. This matrix is square with a given size. + /// + /// the size of the square matrix. + /// + /// If is less than one. + /// + public DiagonalMatrix(int order) : base(order) + { + Data = new double[order * order]; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + public DiagonalMatrix(int rows, int columns) : base(rows, columns) + { + Data = new double[Math.Min(rows, columns)]; + } + + /// + /// Initializes a new instance of the class with all entries set to a particular value. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + /// The value which we assign to each element of the matrix. + public DiagonalMatrix(int rows, int columns, double value) : base(rows, columns) + { + Data = new double[Math.Min(rows, columns)]; + for (var i = 0; i < Data.Length; i++) + { + Data[i] = value; + } + } + + /// + /// Initializes a new instance of the class from a one dimensional array with diagonal elements. This constructor + /// will reference the one dimensional array and not copy it. + /// + /// The number of rows. + /// The number of columns. + /// The one dimensional array which contain diagonal elements. + public DiagonalMatrix(int rows, int columns, double[] diagonalArray) : base(rows, columns) + { + Data = diagonalArray; + } + + /// + /// Initializes a new instance of the class from a 2D array. + /// + /// The 2D array to create this matrix from. + /// When contains an off-diagonal element. + /// Depending on the implementation, an + /// may be thrown if one of the indices is outside the dimensions of the matrix. + public DiagonalMatrix(double[,] array) : this(array.GetLength(0), array.GetLength(1)) + { + var rows = array.GetLength(0); + var columns = array.GetLength(1); + + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < columns; j++) + { + if (i == j) + { + Data[i] = array[i, j]; + } + else if (array[i, j] != 0.0 && !Double.IsNaN(array[i, j])) + { + throw new IndexOutOfRangeException("Cannot set an off-diagonal element in a diagonal matrix."); + } + } + } + } + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + internal double[] Data + { + get; + private set; + } + + /// + /// Retrieves the requested element without range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The requested element. + /// + /// Depending on the implementation, an + /// may be thrown if one of the indices is outside the dimensions of the matrix. + public override double At(int row, int column) + { + return row == column ? Data[row] : 0.0; + } + + /// + /// Sets the value of the given element. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The value to set the element to. + /// + /// When trying to set an off diagonal element. + /// Depending on the implementation, an + /// may be thrown if one of the indices is outside the dimensions of the matrix. + public override void At(int row, int column, double value) + { + if (row == column) + { + Data[row] = value; + } + else if (value != 0.0 && !Double.IsNaN(value)) + { + throw new IndexOutOfRangeException("Cannot set an off-diagonal element in a diagonal matrix."); + } + } + + /// + /// Creates a DiagonalMatrix for the given number of rows and columns. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + /// + /// A DiagonalMatrix with the given dimensions. + /// + public override Matrix CreateMatrix(int numberOfRows, int numberOfColumns) + { + return new DiagonalMatrix(numberOfRows, numberOfColumns); + } + + /// + /// Creates a with a the given dimension. + /// + /// The size of the vector. + /// + /// A with the given dimension. + /// + public override Vector CreateVector(int size) + { + return new SparseVector(size); + } + + /// + /// Sets all values to zero. + /// + public override void Clear() + { + Array.Clear(Data, 0, Data.Length); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// An object to compare with this object. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public override bool Equals(object obj) + { + var diagonalMatrix = obj as DiagonalMatrix; + + if (diagonalMatrix == null) + { + return base.Equals(obj); + } + + // Accept if the argument is the same object as this + if (ReferenceEquals(this, diagonalMatrix)) + { + return true; + } + + if (diagonalMatrix.Data.Length != Data.Length) + { + return false; + } + + // If all else fails, perform element wise comparison. + return !Data.Where((t, i) => t != diagonalMatrix.Data[i]).Any(); + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + var hashNum = Math.Min(Data.Length, 25); + long hash = 0; + for (var i = 0; i < hashNum; i++) + { +#if SILVERLIGHT + hash ^= Precision.DoubleToInt64Bits(Data[i]); +#else + hash ^= BitConverter.DoubleToInt64Bits(Data[i]); +#endif + } + + return BitConverter.ToInt32(BitConverter.GetBytes(hash), 4); + } + + #region Elementary operations + /// + /// Adds another matrix to this matrix. The result will be written into this matrix. + /// + /// The matrix to add to this matrix. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + /// If is not . + public override void Add(Matrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + var m = other as DiagonalMatrix; + if (m == null) + { + throw new ArgumentException(Resources.ArgumentTypeMismatch); + } + + Add(m); + } + + /// + /// Adds another to this matrix. The result will be written into this matrix. + /// + /// The to add to this matrix. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + public void Add(DiagonalMatrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw new ArgumentOutOfRangeException(Resources.ArgumentMatrixDimensions); + } + + Control.LinearAlgebraProvider.AddArrays(Data, other.Data, Data); + } + + /// + /// Subtracts another matrix from this matrix. The result will be written into this matrix. + /// + /// The matrix to subtract. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + /// If is not . + public override void Subtract(Matrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + var m = other as DiagonalMatrix; + if (m == null) + { + throw new ArgumentException(Resources.ArgumentTypeMismatch); + } + + Subtract(m); + } + + /// + /// Subtracts another from this matrix. The result will be written into this matrix. + /// + /// The to subtract. + /// If the other matrix is . + /// If the two matrices don't have the same dimensions. + public void Subtract(DiagonalMatrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw new ArgumentOutOfRangeException(Resources.ArgumentMatrixDimensions); + } + + Control.LinearAlgebraProvider.SubtractArrays(Data, other.Data, Data); + } + + /// + /// Copies the values of the given array to the diagonal. + /// + /// The array to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If is . + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(double[] source) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + + if (source.Length != Data.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, "source"); + } + + Buffer.BlockCopy(source, 0, Data, 0, source.Length * Constants.SizeOfDouble); + } + + /// + /// Copies the values of the given to the diagonal. + /// + /// The vector to copy the values from. The length of the vector should be + /// Min(Rows, Columns). + /// If is . + /// If the length of does not + /// equal Min(Rows, Columns). + /// For non-square matrices, the elements of are copied to + /// this[i,i]. + public override void SetDiagonal(Vector source) + { + var denseSource = source as DenseVector; + if (denseSource == null) + { + base.SetDiagonal(source); + return; + } + + if (Data.Length != denseSource.Data.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "source"); + } + + Buffer.BlockCopy(denseSource.Data, 0, Data, 0, denseSource.Data.Length * Constants.SizeOfDouble); + } + + /// + /// Multiplies each element of this matrix with a scalar. + /// + /// The scalar to multiply with. + public override void Multiply(double scalar) + { + if (scalar == 0.0) + { + Clear(); + return; + } + + if (scalar == 1.0) + { + return; + } + + Control.LinearAlgebraProvider.ScaleArray(scalar, Data); + } + + /// + /// Multiplies this diagonal matrix with another diagonal matrix and places the results into the result diagonal matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If the other matrix is . + /// If the result matrix is . + /// If this.Columns != other.Rows. + /// If the result matrix's dimensions are not the this.Rows x other.Columns. + public override void Multiply(Matrix other, Matrix result) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (ColumnCount != other.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (result.RowCount != RowCount || result.ColumnCount != other.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var m = other as DiagonalMatrix; + var r = result as DiagonalMatrix; + + if (m == null || r == null) + { + base.Multiply(other, result); + } + else + { + var thisDataCopy = new double[r.Data.Length]; + var otherDataCopy = new double[r.Data.Length]; + Buffer.BlockCopy(Data, 0, thisDataCopy, 0, (r.Data.Length > Data.Length) ? Data.Length * Constants.SizeOfDouble : r.Data.Length * Constants.SizeOfDouble); + Buffer.BlockCopy(m.Data, 0, otherDataCopy, 0, (r.Data.Length > m.Data.Length) ? m.Data.Length * Constants.SizeOfDouble : r.Data.Length * Constants.SizeOfDouble); + + Control.LinearAlgebraProvider.PointWiseMultiplyArrays(thisDataCopy, otherDataCopy, r.Data); + } + } + + /// + /// Multiplies this matrix with another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.Rows. + /// If the other matrix is . + /// The result of multiplication. + public override Matrix Multiply(Matrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (ColumnCount != other.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var m = other as DiagonalMatrix; + if (m == null) + { + return base.Multiply(other); + } + + var result = (DiagonalMatrix)CreateMatrix(RowCount, other.ColumnCount); + Multiply(other, result); + return result; + } + + /// + /// Multiplies this matrix with a vector and places the results into the result matrix. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If is . + /// If is . + /// If result.Count != this.RowCount. + /// If this.ColumnCount != .Count. + public override void Multiply(Vector rightSide, Vector result) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (ColumnCount != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "rightSide"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (RowCount != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + if (ReferenceEquals(rightSide, result)) + { + var tmp = result.CreateVector(result.Count); + Multiply(rightSide, tmp); + tmp.CopyTo(result); + } + else + { + // Clear the result vector + result.Clear(); + + // Multiply the elements in the vector with the corresponding diagonal element in this. + for (var r = 0; r < Data.Length; r++) + { + result[r] = Data[r] * rightSide[r]; + } + } + } + + /// + /// Left multiply a matrix with a vector ( = vector * matrix ) and place the result in the result vector. + /// + /// The vector to multiply with. + /// The result of the multiplication. + /// If is . + /// If the result matrix is . + /// If result.Count != this.ColumnCount. + /// If this.RowCount != .Count. + public override void LeftMultiply(Vector leftSide, Vector result) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (RowCount != leftSide.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "leftSide"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (ColumnCount != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + if (ReferenceEquals(leftSide, result)) + { + var tmp = result.CreateVector(result.Count); + LeftMultiply(leftSide, tmp); + tmp.CopyTo(result); + } + else + { + // Clear the result vector + result.Clear(); + + // Multiply the elements in the vector with the corresponding diagonal element in this. + for (var r = 0; r < Data.Length; r++) + { + result[r] = Data[r] * leftSide[r]; + } + } + } + + /// + /// Computes the determinant of this matrix. + /// + /// The determinant of this matrix. + public override double Determinant() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return Data.Aggregate(1.0, (current, t) => current * t); + } + + /// + /// Returns the elements of the diagonal in a . + /// + /// The elements of the diagonal. + /// For non-square matrices, the method returns Min(Rows, Columns) elements where + /// i == j (i is the row index, and j is the column index). + public override Vector Diagonal() + { + // TODO: Should we return reference to array? In current implementation we return copy of array, so changes in DenseVector will + // not influence onto diagonal elements + return new DenseVector((double[])Data.Clone()); + } + + /// + /// Multiplies this diagonal matrix with transpose of another diagonal matrix and places the results into the result diagonal matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If the other matrix is . + /// If the result matrix is . + /// If this.Columns != other.Rows. + /// If the result matrix's dimensions are not the this.Rows x other.Columns. + public override void TransposeAndMultiply(Matrix other, Matrix result) + { + var otherDiagonal = other as DiagonalMatrix; + var resultDiagonal = result as DiagonalMatrix; + + if (otherDiagonal == null || resultDiagonal == null) + { + base.TransposeAndMultiply(other, result); + return; + } + + Multiply(otherDiagonal.Transpose(), result); + } + + /// + /// Multiplies this matrix with transpose of another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.Rows. + /// If the other matrix is . + /// The result of multiplication. + public override Matrix TransposeAndMultiply(Matrix other) + { + var otherDiagonal = other as DiagonalMatrix; + if (otherDiagonal == null) + { + return base.TransposeAndMultiply(other); + } + + if (ColumnCount != otherDiagonal.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var result = (DiagonalMatrix)CreateMatrix(RowCount, other.RowCount); + TransposeAndMultiply(other, result); + return result; + } + + /// + /// Multiplies two diagonal matrices. + /// + /// The left matrix to multiply. + /// The right matrix to multiply. + /// The result of multiplication. + /// If or is . + /// If the dimensions of or don't conform. + public static DiagonalMatrix operator *(DiagonalMatrix leftSide, DiagonalMatrix rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide.ColumnCount != rightSide.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + return (DiagonalMatrix)leftSide.Multiply(rightSide); + } + + #endregion + + /// + /// Copies the elements of this matrix to the given matrix. + /// + /// + /// The matrix to copy values into. + /// + /// + /// If target is . + /// + /// + /// If this and the target matrix do not have the same dimensions.. + /// + public override void CopyTo(Matrix target) + { + var diagonalTarget = target as DiagonalMatrix; + + if (diagonalTarget == null) + { + base.CopyTo(target); + return; + } + + if (ReferenceEquals(this, target)) + { + return; + } + + if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "target"); + } + + Buffer.BlockCopy(Data, 0, diagonalTarget.Data, 0, Data.Length * Constants.SizeOfDouble); + } + + /// + /// Returns the transpose of this matrix. + /// + /// The transpose of this matrix. + public override Matrix Transpose() + { + var ret = new DiagonalMatrix(ColumnCount, RowCount); + Buffer.BlockCopy(Data, 0, ret.Data, 0, Data.Length * Constants.SizeOfDouble); + return ret; + } + + /// + /// Copies the requested column elements into the given vector. + /// + /// The column to copy elements from. + /// The row to start copying from. + /// The number of elements to copy. + /// The to copy the column into. + /// If the result is . + /// If is negative, + /// or greater than or equal to the number of columns. + /// If is negative, + /// or greater than or equal to the number of rows. + /// If + + /// is greater than or equal to the number of rows. + /// If is not positive. + /// If result.Count < length. + public override void Column(int columnIndex, int rowIndex, int length, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (columnIndex >= ColumnCount || columnIndex < 0) + { + throw new ArgumentOutOfRangeException("columnIndex"); + } + + if (rowIndex >= RowCount || rowIndex < 0) + { + throw new ArgumentOutOfRangeException("rowIndex"); + } + + if (rowIndex + length > RowCount) + { + throw new ArgumentOutOfRangeException("length"); + } + + if (length < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "length"); + } + + if (result.Count < length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + // Clear the result and copy the diagonal entry. + result.Clear(); + if (columnIndex >= rowIndex && columnIndex < rowIndex + length && columnIndex < Data.Length) + { + result[columnIndex - rowIndex] = Data[columnIndex]; + } + } + + /// + /// Copies the requested row elements into a new . + /// + /// The row to copy elements from. + /// The column to start copying from. + /// The number of elements to copy. + /// The to copy the column into. + /// If the result is . + /// If is negative, + /// or greater than or equal to the number of columns. + /// If is negative, + /// or greater than or equal to the number of rows. + /// If + + /// is greater than or equal to the number of rows. + /// If is not positive. + /// If result.Count < length. + public override void Row(int rowIndex, int columnIndex, int length, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (rowIndex >= RowCount || rowIndex < 0) + { + throw new ArgumentOutOfRangeException("rowIndex"); + } + + if (columnIndex >= ColumnCount || columnIndex < 0) + { + throw new ArgumentOutOfRangeException("columnIndex"); + } + + if (columnIndex + length > ColumnCount) + { + throw new ArgumentOutOfRangeException("length"); + } + + if (length < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "length"); + } + + if (result.Count < length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + // Clear the result and copy the diagonal entry. + result.Clear(); + if (rowIndex >= columnIndex && rowIndex < columnIndex + length && rowIndex < Data.Length) + { + result[rowIndex - columnIndex] = Data[rowIndex]; + } + } + + /// Calculates the L1 norm. + /// The L1 norm of the matrix. + public override double L1Norm() + { + return Data.Aggregate(double.NegativeInfinity, (current, t) => Math.Max(current, Math.Abs(t))); + } + + /// Calculates the L2 norm. + /// The L2 norm of the matrix. + public override double L2Norm() + { + return Data.Aggregate(double.NegativeInfinity, (current, t) => Math.Max(current, Math.Abs(t))); + } + + /// Calculates the Frobenius norm of this matrix. + /// The Frobenius norm of this matrix. + public override double FrobeniusNorm() + { + var norm = Data.Sum(t => t * t); + return Math.Sqrt(norm); + } + + /// Calculates the infinity norm of this matrix. + /// The infinity norm of this matrix. + public override double InfinityNorm() + { + return L1Norm(); + } + + /// Calculates the condition number of this matrix. + /// The condition number of the matrix. + public override double ConditionNumber() + { + var maxSv = double.NegativeInfinity; + var minSv = double.PositiveInfinity; + for (var i = 0; i < Data.Length; i++) + { + maxSv = Math.Max(maxSv, Math.Abs(Data[i])); + minSv = Math.Min(minSv, Math.Abs(Data[i])); + } + + return maxSv / minSv; + } + + /// Computes the inverse of this matrix. + /// If is not a square matrix. + /// If is singular. + /// The inverse of this matrix. + public override Matrix Inverse() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var inverse = (DiagonalMatrix)Clone(); + for (var i = 0; i < Data.Length; i++) + { + if (Data[i] != 0.0) + { + inverse.Data[i] = 1.0 / Data[i]; + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixNotSingular); + } + } + + return inverse; + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + return Clone(); + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void LowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + if (ReferenceEquals(this, result)) + { + return; + } + + result.Clear(); + for (var i = 0; i < Data.Length; i++) + { + result[i, i] = Data[i]; + } + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix StrictlyLowerTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyLowerTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + result.Clear(); + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + return Clone(); + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void UpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + result.Clear(); + for (var i = 0; i < Data.Length; i++) + { + result[i, i] = Data[i]; + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. The new matrix + /// does not contain the diagonal elements of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix StrictlyUpperTriangle() + { + return new DiagonalMatrix(RowCount, ColumnCount); + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + /// If is . + /// If the result matrix's dimensions are not the same as this matrix. + public override void StrictlyUpperTriangle(Matrix result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.RowCount != RowCount || result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + result.Clear(); + } + + /// + /// Creates a matrix that contains the values from the requested sub-matrix. + /// + /// The row to start copying from. + /// The number of rows to copy. Must be positive. + /// The column to start copying from. + /// The number of columns to copy. Must be positive. + /// The requested sub-matrix. + /// If: is + /// negative, or greater than or equal to the number of rows. + /// is negative, or greater than or equal to the number + /// of columns. + /// (columnIndex + columnLength) >= Columns + /// (rowIndex + rowLength) >= Rows + /// If or + /// is not positive. + public override Matrix SubMatrix(int rowIndex, int rowLength, int columnIndex, int columnLength) + { + if (rowIndex >= RowCount || rowIndex < 0) + { + throw new ArgumentOutOfRangeException("rowIndex"); + } + + if (columnIndex >= ColumnCount || columnIndex < 0) + { + throw new ArgumentOutOfRangeException("columnIndex"); + } + + if (rowLength < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "rowLength"); + } + + if (columnLength < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "columnLength"); + } + + var colMax = columnIndex + columnLength; + var rowMax = rowIndex + rowLength; + + if (rowMax > RowCount) + { + throw new ArgumentOutOfRangeException("rowLength"); + } + + if (colMax > ColumnCount) + { + throw new ArgumentOutOfRangeException("columnLength"); + } + + var result = new SparseMatrix(rowLength, columnLength); + + if (rowIndex > columnIndex && columnIndex + columnLength > rowIndex) + { + for (var i = 0; rowIndex - columnIndex + i < Math.Min(columnLength, rowLength); i++) + { + result[i, rowIndex - columnIndex + i] = Data[rowIndex + i]; + } + } + else if (rowIndex < columnIndex && rowIndex + rowLength > columnIndex) + { + for (var i = 0; rowIndex - columnIndex + i < Math.Min(columnLength, rowLength); i++) + { + result[columnIndex - rowIndex + i, i] = Data[columnIndex + i]; + } + } + else + { + for (var i = 0; i < Math.Min(columnLength, rowLength); i++) + { + result[i, i] = Data[rowIndex + i]; + } + } + + return result; + } + + /// + /// Returns this matrix as a multidimensional array. + /// + /// A multidimensional containing the values of this matrix. + public override double[,] ToArray() + { + var result = new double[RowCount, ColumnCount]; + for (var i = 0; i < Data.Length; i++) + { + result[i, i] = Data[i]; + } + + return result; + } + + /// + /// Creates a new and inserts the given column at the given index. + /// + /// The index of where to insert the column. + /// The column to insert. + /// A new with the inserted column. + /// If is . + /// If is < zero or > the number of columns. + /// If the size of != the number of rows. + public override Matrix InsertColumn(int columnIndex, Vector column) + { + if (column == null) + { + throw new ArgumentNullException("column"); + } + + if (columnIndex < 0 || columnIndex > ColumnCount) + { + throw new ArgumentOutOfRangeException("columnIndex"); + } + + if (column.Count != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, "column"); + } + + var result = new SparseMatrix(RowCount, ColumnCount + 1); + + for (var i = 0; i < columnIndex; i++) + { + result.SetColumn(i, Column(i)); + } + + result.SetColumn(columnIndex, column); + + for (var i = columnIndex + 1; i < ColumnCount + 1; i++) + { + result.SetColumn(i, Column(i - 1)); + } + + return result; + } + + /// + /// Creates a new and inserts the given row at the given index. + /// + /// The index of where to insert the row. + /// The row to insert. + /// A new with the inserted column. + /// If is . + /// If is < zero or > the number of rows. + /// If the size of != the number of columns. + public override Matrix InsertRow(int rowIndex, Vector row) + { + if (row == null) + { + throw new ArgumentNullException("row"); + } + + if (rowIndex < 0 || rowIndex > RowCount) + { + throw new ArgumentOutOfRangeException("rowIndex"); + } + + if (row.Count != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension, "row"); + } + + var result = new SparseMatrix(RowCount + 1, ColumnCount); + + for (var i = 0; i < rowIndex; i++) + { + result.SetRow(i, Row(i)); + } + + result.SetRow(rowIndex, row); + + for (var i = rowIndex + 1; i < RowCount; i++) + { + result.SetRow(i, Row(i - 1)); + } + + return result; + } + + /// + /// Stacks this matrix on top of the given matrix and places the result into the result . + /// + /// The matrix to stack this matrix upon. + /// The combined . + /// If lower is . + /// If upper.Columns != lower.Columns. + public override Matrix Stack(Matrix lower) + { + if (lower == null) + { + throw new ArgumentNullException("lower"); + } + + if (lower.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, "lower"); + } + + var result = new SparseMatrix(RowCount + lower.RowCount, ColumnCount); + Stack(lower, result); + return result; + } + + /// + /// Stacks this matrix on top of the given matrix and places the result into the result . + /// + /// The matrix to stack this matrix upon. + /// The combined . + /// If lower is . + /// If upper.Columns != lower.Columns. + public override void Stack(Matrix lower, Matrix result) + { + if (lower == null) + { + throw new ArgumentNullException("lower"); + } + + if (lower.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension, "lower"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.RowCount != (RowCount + lower.RowCount) || result.ColumnCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + // Clear the result matrix + result.Clear(); + + // Copy the diagonal part into the result matrix. + for (var i = 0; i < Data.Length; i++) + { + result[i, i] = Data[i]; + } + + // Copy the lower matrix into the result matrix. + for (var i = 0; i < lower.RowCount; i++) + { + for (var j = 0; j < lower.ColumnCount; j++) + { + result[i + RowCount, j] = lower[i, j]; + } + } + } + + /// + /// Concatenates this matrix with the given matrix. + /// + /// The matrix to concatenate. + /// The combined . + public override Matrix Append(Matrix right) + { + if (right == null) + { + throw new ArgumentNullException("right"); + } + + if (right.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + var result = new SparseMatrix(RowCount, ColumnCount + right.ColumnCount); + Append(right, result); + return result; + } + + /// + /// Concatenates this matrix with the given matrix and places the result into the result . + /// + /// The matrix to concatenate. + /// The combined . + public override void Append(Matrix right, Matrix result) + { + if (right == null) + { + throw new ArgumentNullException("right"); + } + + if (right.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.ColumnCount != (ColumnCount + right.ColumnCount) || result.RowCount != RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // Clear the result matrix + result.Clear(); + + // Copy the diagonal part into the result matrix. + for (var i = 0; i < Data.Length; i++) + { + result[i, i] = Data[i]; + } + + // Copy the lower matrix into the result matrix. + for (var i = 0; i < right.RowCount; i++) + { + for (var j = 0; j < right.ColumnCount; j++) + { + result[i, j + RowCount] = right[i, j]; + } + } + } + + /// + /// Diagonally stacks his matrix on top of the given matrix. The new matrix is a M-by-N matrix, + /// where M = this.Rows + lower.Rows and N = this.Columns + lower.Columns. + /// The values of off the off diagonal matrices/blocks are set to zero. + /// + /// The lower, right matrix. + /// If lower is . + /// the combined matrix + public override Matrix DiagonalStack(Matrix lower) + { + if (lower == null) + { + throw new ArgumentNullException("lower"); + } + + var result = new SparseMatrix(RowCount + lower.RowCount, ColumnCount + lower.ColumnCount); + DiagonalStack(lower, result); + return result; + } + + /// + /// Diagonally stacks his matrix on top of the given matrix and places the combined matrix into the result matrix. + /// + /// The lower, right matrix. + /// The combined matrix + /// If lower is . + /// If the result matrix is . + /// If the result matrix's dimensions are not (this.Rows + lower.rows) x (this.Columns + lower.Columns). + public override void DiagonalStack(Matrix lower, Matrix result) + { + if (lower == null) + { + throw new ArgumentNullException("lower"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.RowCount != RowCount + lower.RowCount || result.ColumnCount != ColumnCount + lower.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + // Clear the result matrix + result.Clear(); + + // Copy the diagonal part into the result matrix. + for (var i = 0; i < Data.Length; i++) + { + result[i, i] = Data[i]; + } + + // Copy the lower matrix into the result matrix. + CommonParallel.For(0, lower.RowCount, i => CommonParallel.For(0, lower.ColumnCount, j => result.At(i + RowCount, j + ColumnCount, lower.At(i, j)))); + } + + /// + /// Pointwise multiplies this matrix with another matrix and stores the result into the result matrix. + /// + /// The matrix to pointwise multiply with this one. + /// The matrix to store the result of the pointwise multiplication. + /// If the other matrix is . + /// If the result matrix is . + /// If this matrix and are not the same size. + /// If this matrix and are not the same size. + public override void PointwiseMultiply(Matrix other, Matrix result) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (ColumnCount != other.ColumnCount || RowCount != other.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + if (ColumnCount != result.ColumnCount || RowCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + var m = other as DiagonalMatrix; + var r = result as DiagonalMatrix; + + if (m == null || r == null) + { + base.PointwiseMultiply(other, result); + } + else + { + Control.LinearAlgebraProvider.PointWiseMultiplyArrays(Data, m.Data, r.Data); + } + } + + /// + /// Permute the columns of a matrix according to a permutation. + /// + /// The column permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteColumns(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + + /// + /// Permute the rows of a matrix according to a permutation. + /// + /// The row permutation to apply to this matrix. + /// Always thrown + /// Permutation in diagonal matrix are senseless, because of matrix nature + public override void PermuteRows(Permutation p) + { + throw new InvalidOperationException("Permutations in diagonal matrix are not allowed"); + } + #region Static constructors for special matrices. + + /// + /// Initializes a square with all zero's except for ones on the diagonal. + /// + /// the size of the square matrix. + /// A diagonal identity matrix. + /// + /// If is less than one. + /// + public static DiagonalMatrix Identity(int order) + { + var m = new DiagonalMatrix(order); + for (var i = 0; i < order; i++) + { + m.Data[i] = 1.0; + } + + return m; + } + + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs b/src/Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs index e41f8755..31426538 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/DenseQR.cs @@ -50,6 +50,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// /// The matrix to factor. /// If is null. + /// If row count is less then column count public DenseQR(DenseMatrix matrix) { if (matrix == null) diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/ExtensionMethods.cs b/src/Numerics/LinearAlgebra/Double/Factorization/ExtensionMethods.cs index efd621ea..3a9ccf36 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/ExtensionMethods.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/ExtensionMethods.cs @@ -65,6 +65,17 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization return Factorization.QR.Create(matrix); } + /// + /// Computes the QR decomposition for a matrix using Modified Gram-Schmidt Orthogonalization. + /// + /// The matrix to factor. + /// The QR decomposition object. + public static GramSchmidt GramSchmidt(this Matrix matrix) + { + // NOTE: There is no factory for GramSchmidt. Use constructor of GramSchmidt class. + return new GramSchmidt(matrix); + } + /// /// Computes the SVD decomposition for a matrix. /// diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs b/src/Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs new file mode 100644 index 00000000..b1e306d1 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs @@ -0,0 +1,284 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using Properties; + + /// + /// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. + /// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. + /// + /// + /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. + /// + public class GramSchmidt : QR + { + /// + /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public GramSchmidt(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount < matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + MatrixQ = matrix.Clone(); + MatrixR = matrix.CreateMatrix(matrix.ColumnCount, matrix.ColumnCount); + + for (var k = 0; k < MatrixQ.ColumnCount; k++) + { + var norm = MatrixQ.Column(k).Norm(2); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + MatrixR.At(k, k, norm); + for (var i = 0; i < MatrixQ.RowCount; i++) + { + MatrixQ.At(i, k, MatrixQ.At(i, k) / norm); + } + + for (var j = k + 1; j < MatrixQ.ColumnCount; j++) + { + var dot = MatrixQ.Column(k).DotProduct(MatrixQ.Column(j)); + MatrixR.At(k, j, dot); + for (var i = 0; i < MatrixQ.RowCount; i++) + { + var value = MatrixQ.At(i, j) - (MatrixQ.At(i, k) * dot); + MatrixQ.At(i, j, value); + } + } + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public override bool IsFullRank + { + get + { + return true; + } + } + + /// + /// Gets the determinant of the matrix for which the QR matrix was computed. + /// + public override double Determinant + { + get + { + if (MatrixQ.RowCount != MatrixQ.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var det = 1.0; + for (var i = 0; i < MatrixR.ColumnCount; i++) + { + det *= MatrixR.At(i, i); + if (Math.Abs(MatrixR.At(i, i)).AlmostEqualInDecimalPlaces(0.0, 15)) + { + return 0; + } + } + + return Math.Abs(det); + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (MatrixQ.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (MatrixQ.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new double[MatrixQ.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < MatrixQ.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < MatrixQ.ColumnCount; i++) + { + double s = 0; + for (var k = 0; k < MatrixQ.RowCount; k++) + { + s += MatrixQ.At(k, i) * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = MatrixQ.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / MatrixR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * MatrixR.At(i, k))); + } + } + } + + for (var i = 0; i < MatrixR.ColumnCount; i++) + { + for (var j = 0; j < input.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (MatrixQ.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (MatrixQ.ColumnCount != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new double[MatrixQ.RowCount]; + for (var k = 0; k < MatrixQ.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < MatrixQ.ColumnCount; i++) + { + double s = 0; + for (var k = 0; k < MatrixQ.RowCount; k++) + { + s += MatrixQ.At(k, i) * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = MatrixQ.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= MatrixR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * MatrixR.At(i, k); + } + } + + for (var i = 0; i < MatrixR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs b/src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs new file mode 100644 index 00000000..c0978f59 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs @@ -0,0 +1,223 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using Properties; + + /// + /// A class which encapsulates the functionality of a Cholesky factorization for soarse matrices. + /// For a symmetric, positive definite matrix A, the Cholesky factorization + /// is an lower triangular matrix L so that A = L*L'. + /// + /// + /// The computation of the Cholesky factorization is done at construction time. If the matrix is not symmetric + /// or positive definite, the constructor will throw an exception. + /// + public class SparseCholesky : Cholesky + { + /// + /// Initializes a new instance of the class. This object will compute the + /// Cholesky factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + /// If is not positive definite. + public SparseCholesky(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create a new matrix for the Cholesky factor, then perform factorization (while overwriting). + CholeskyFactor = matrix.Clone(); + for (var j = 0; j < CholeskyFactor.RowCount; j++) + { + var d = 0.0; + for (var k = 0; k < j; k++) + { + var s = 0.0; + for (var i = 0; i < k; i++) + { + s += CholeskyFactor.At(k, i) * CholeskyFactor.At(j, i); + } + + s = (matrix.At(j, k) - s) / CholeskyFactor.At(k, k); + CholeskyFactor.At(j, k, s); + d += s * s; + } + + d = matrix.At(j, j) - d; + if (d <= 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + CholeskyFactor.At(j, j, Math.Sqrt(d)); + for (var k = j + 1; k < CholeskyFactor.RowCount; k++) + { + CholeskyFactor.At(j, k, 0.0); + } + } + } + + + /// + /// Solves a system of linear equations, AX = B, with A Cholesky factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != CholeskyFactor.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + input.CopyTo(result); + var order = CholeskyFactor.RowCount; + + for (var c = 0; c < result.ColumnCount; c++) + { + // Solve L*Y = B; + double sum; + for (var i = 0; i < order; i++) + { + sum = result.At(i, c); + for (var k = i - 1; k >= 0; k--) + { + sum -= CholeskyFactor.At(i, k) * result.At(k, c); + } + + result.At(i, c, sum / CholeskyFactor.At(i, i)); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result.At(i, c); + for (var k = i + 1; k < order; k++) + { + sum -= CholeskyFactor.At(k, i) * result.At(k, c); + } + + result.At(i, c, sum / CholeskyFactor.At(i, i)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A Cholesky factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != CholeskyFactor.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + input.CopyTo(result); + var order = CholeskyFactor.RowCount; + + // Solve L*Y = B; + double sum; + for (var i = 0; i < order; i++) + { + sum = result[i]; + for (var k = i - 1; k >= 0; k--) + { + sum -= CholeskyFactor.At(i, k) * result[k]; + } + + result[i] = sum / CholeskyFactor.At(i, i); + } + + // Solve L'*X = Y; + for (var i = order - 1; i >= 0; i--) + { + sum = result[i]; + for (var k = i + 1; k < order; k++) + { + sum -= CholeskyFactor.At(k, i) * result[k]; + } + + result[i] = sum / CholeskyFactor.At(i, i); + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/SparseLU.cs b/src/Numerics/LinearAlgebra/Double/Factorization/SparseLU.cs new file mode 100644 index 00000000..485e8e3d --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/SparseLU.cs @@ -0,0 +1,300 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using Properties; + + /// + /// A class which encapsulates the functionality of an LU factorization. + /// For a matrix A, the LU factorization is a pair of lower triangular matrix L and + /// upper triangular matrix U so that A = L*U. + /// + /// + /// The computation of the LU factorization is done at construction time. + /// + public class SparseLU : LU + { + /// + /// Initializes a new instance of the class. This object will compute the + /// LU factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + /// If is not a square matrix. + public SparseLU(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + // Create an array for the pivot indices. + var order = matrix.RowCount; + Factors = matrix.Clone(); + Pivots = new int[order]; + + // Initialize the pivot matrix to the identity permutation. + for (var i = 0; i < order; i++) + { + Pivots[i] = i; + } + + var vectorLUcolj = new double[order]; + for (var j = 0; j < order; j++) + { + // Make a copy of the j-th column to localize references. + for (var i = 0; i < order; i++) + { + vectorLUcolj[i] = Factors.At(i, j); + } + + // Apply previous transformations. + for (var i = 0; i < order; i++) + { + var kmax = Math.Min(i, j); + var s = 0.0; + for (var k = 0; k < kmax; k++) + { + s += Factors.At(i, k) * vectorLUcolj[k]; + } + + vectorLUcolj[i] -= s; + Factors.At(i, j, vectorLUcolj[i]); + } + + // Find pivot and exchange if necessary. + var p = j; + for (var i = j + 1; i < order; i++) + { + if (Math.Abs(vectorLUcolj[i]) > Math.Abs(vectorLUcolj[p])) + { + p = i; + } + } + + if (p != j) + { + for (var k = 0; k < order; k++) + { + var temp = Factors.At(p, k); + Factors.At(p, k, Factors.At(j, k)); + Factors.At(j, k, temp); + } + + Pivots[j] = p; + } + + // Compute multipliers. + if (j < order & Factors.At(j, j) != 0.0) + { + for (var i = j + 1; i < order; i++) + { + Factors.At(i, j, (Factors.At(i, j) / Factors.At(j, j))); + } + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A LU factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // Check for proper dimensions. + if (result.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + if (result.ColumnCount != input.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (input.RowCount != Factors.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(p, j); + result.At(p, j, result.At(i, j)); + result.At(i, j, temp); + } + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + for (var j = 0; j < result.ColumnCount; j++) + { + result.At(k, j, (result.At(k, j) / Factors.At(k, k))); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + var temp = result.At(k, j) * Factors.At(i, k); + result.At(i, j, result.At(i, j) - temp); + } + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A LU factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // Check for proper dimensions. + if (input.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + if (input.Count != Factors.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + // Copy the contents of input to result. + input.CopyTo(result); + for (var i = 0; i < Pivots.Length; i++) + { + if (Pivots[i] == i) + { + continue; + } + + var p = Pivots[i]; + var temp = result[p]; + result[p] = result[i]; + result[i] = temp; + } + + var order = Factors.RowCount; + + // Solve L*Y = P*B + for (var k = 0; k < order; k++) + { + for (var i = k + 1; i < order; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + + // Solve U*X = Y; + for (var k = order - 1; k >= 0; k--) + { + result[k] /= Factors.At(k, k); + for (var i = 0; i < k; i++) + { + result[i] -= result[k] * Factors.At(i, k); + } + } + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var order = Factors.RowCount; + var inverse = Factors.CreateMatrix(order, order); + for (var i = 0; i < order; i++) + { + inverse.At(i, i, 1.0); + } + + return Solve(inverse); + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/SparseQR.cs b/src/Numerics/LinearAlgebra/Double/Factorization/SparseQR.cs new file mode 100644 index 00000000..a7e419be --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/SparseQR.cs @@ -0,0 +1,332 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using System.Linq; + using Properties; + + /// + /// A class which encapsulates the functionality of the QR decomposition. + /// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal matrix + /// (its columns are orthogonal unit vectors meaning QTQ = I) and R is an upper triangular matrix + /// (also called right triangular matrix). + /// + /// + /// The computation of the QR decomposition is done at construction time by Householder transformation. + /// + public class SparseQR : QR + { + /// + /// Initializes a new instance of the class. This object will compute the + /// QR factorization when the constructor is called and cache it's factorization. + /// + /// The matrix to factor. + /// If is null. + public SparseQR(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount < matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + MatrixR = matrix.Clone(); + MatrixQ = matrix.CreateMatrix(matrix.RowCount, matrix.RowCount); + + for (var i = 0; i < matrix.RowCount; i++) + { + MatrixQ.At(i, i, 1.0); + } + + var minmn = Math.Min(matrix.RowCount, matrix.ColumnCount); + var u = new double[minmn][]; + for (var i = 0; i < minmn; i++) + { + u[i] = GenerateColumn(MatrixR, i, matrix.RowCount - 1, i); + ComputeQR(u[i], MatrixR, i, matrix.RowCount - 1, i + 1, matrix.ColumnCount - 1); + } + + for (var i = minmn - 1; i >= 0; i--) + { + ComputeQR(u[i], MatrixQ, i, matrix.RowCount - 1, i, matrix.RowCount - 1); + } + } + + /// + /// Generate column from initial matrix to work array + /// + /// Initial matrix + /// The firts row + /// The last row + /// Column index + /// Generated vector + private static double[] GenerateColumn(Matrix a, int rowStart, int rowEnd, int column) + { + var ru = rowEnd - rowStart + 1; + var u = new double[ru]; + + for (var i = rowStart; i <= rowEnd; i++) + { + u[i - rowStart] = a.At(i, rowStart); + a.At(i, rowStart, 0.0); + } + + var norm = u.Sum(t => t * t); + norm = Math.Sqrt(norm); + + if (rowStart == rowEnd || norm == 0) + { + a.At(rowStart, column, -u[0]); + u[0] = Math.Sqrt(2.0); + return u; + } + + var scale = 1.0 / norm; + if (u[0] < 0.0) + { + scale *= -1.0; + } + + a.At(rowStart, column, -1.0 / scale); + + for (var i = 0; i < ru; i++) + { + u[i] *= scale; + } + + u[0] += 1.0; + var s = Math.Sqrt(1.0 / u[0]); + + for (var i = 0; i < ru; i++) + { + u[i] *= s; + } + + return u; + } + + /// + /// Perform calculation of Q or R + /// + /// Work array + /// Q or R matrices + /// The first row + /// The last row + /// The first column + /// The last column + private static void ComputeQR(double[] u, Matrix a, int rowStart, int rowEnd, int columnStart, int columnEnd) + { + if (rowEnd < rowStart || columnEnd < columnStart) + { + return; + } + + var v = new double[columnEnd - columnStart + 1]; + for (var j = columnStart; j <= columnEnd; j++) + { + v[j - columnStart] = 0.0; + } + + for (var i = rowStart; i <= rowEnd; i++) + { + for (var j = columnStart; j <= columnEnd; j++) + { + v[j - columnStart] = v[j - columnStart] + (u[i - rowStart] * a.At(i, j)); + } + } + + for (var i = rowStart; i <= rowEnd; i++) + { + for (var j = columnStart; j <= columnEnd; j++) + { + a.At(i, j, a.At(i, j) - (u[i - rowStart] * v[j - columnStart])); + } + } + } + + /// + /// Solves a system of linear equations, AX = B, with A QR factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (MatrixR.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (MatrixR.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var bn = inputCopy.ColumnCount; + var column = new double[MatrixR.RowCount]; + for (var j = 0; j < bn; j++) + { + for (var k = 0; k < MatrixR.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < MatrixR.RowCount; i++) + { + double s = 0; + for (var k = 0; k < MatrixR.RowCount; k++) + { + s += MatrixQ.At(k, i) * column[k]; + } + + inputCopy.At(i, j, s); + } + } + + // Solve R*X = Y; + for (var k = MatrixR.ColumnCount - 1; k >= 0; k--) + { + for (var j = 0; j < bn; j++) + { + inputCopy.At(k, j, inputCopy.At(k, j) / MatrixR.At(k, k)); + } + + for (var i = 0; i < k; i++) + { + for (var j = 0; j < bn; j++) + { + inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * MatrixR.At(i, k))); + } + } + } + + for (var i = 0; i < MatrixR.ColumnCount; i++) + { + for (var j = 0; j < inputCopy.ColumnCount; j++) + { + result.At(i, j, inputCopy.At(i, j)); + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A QR factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (MatrixR.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (MatrixR.ColumnCount != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var inputCopy = input.Clone(); + + // Compute Y = transpose(Q)*B + var column = new double[MatrixR.RowCount]; + for (var k = 0; k < MatrixR.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < MatrixR.RowCount; i++) + { + double s = 0; + for (var k = 0; k < MatrixR.RowCount; k++) + { + s += MatrixQ.At(k, i) * column[k]; + } + + inputCopy[i] = s; + } + + // Solve R*X = Y; + for (var k = MatrixR.ColumnCount - 1; k >= 0; k--) + { + inputCopy[k] /= MatrixR.At(k, k); + for (var i = 0; i < k; i++) + { + inputCopy[i] -= inputCopy[k] * MatrixR.At(i, k); + } + } + + for (var i = 0; i < MatrixR.ColumnCount; i++) + { + result[i] = inputCopy[i]; + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/SparseSvd.cs b/src/Numerics/LinearAlgebra/Double/Factorization/SparseSvd.cs new file mode 100644 index 00000000..35df929e --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/SparseSvd.cs @@ -0,0 +1,923 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using Properties; + + /// + /// A class which encapsulates the functionality of the singular value decomposition (SVD) for . + /// Suppose M is an m-by-n matrix whose entries are real numbers. + /// Then there exists a factorization of the form M = UΣVT where: + /// - U is an m-by-m unitary matrix; + /// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal; + /// - VT denotes transpose of V, an n-by-n unitary matrix; + /// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal + /// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined + /// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M. + /// + /// + /// The computation of the singular value decomposition is done at construction time. + /// + public class SparseSvd : Svd + { + /// + /// Initializes a new instance of the class. This object will compute the + /// the singular value decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// Compute the singular U and VT vectors or not. + /// If is null. + /// If SVD algorithm failed to converge with matrix . + public SparseSvd(Matrix matrix, bool computeVectors) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + ComputeVectors = computeVectors; + var nm = Math.Min(matrix.RowCount + 1, matrix.ColumnCount); + var matrixCopy = matrix.Clone(); + + VectorS = matrixCopy.CreateVector(nm); + MatrixU = matrixCopy.CreateMatrix(matrixCopy.RowCount, matrixCopy.RowCount); + MatrixVT = matrixCopy.CreateMatrix(matrixCopy.ColumnCount, matrixCopy.ColumnCount); + + const int Maxiter = 1000; + var e = new double[matrixCopy.ColumnCount]; + var work = new double[matrixCopy.RowCount]; + + int i, j; + int l, lp1; + var cs = 0.0; + var sn = 0.0; + double t; + + var ncu = matrixCopy.RowCount; + + // Reduce matrixCopy to bidiagonal form, storing the diagonal elements + // In s and the super-diagonal elements in e. + var nct = Math.Min(matrixCopy.RowCount - 1, matrixCopy.ColumnCount); + var nrt = Math.Max(0, Math.Min(matrixCopy.ColumnCount - 2, matrixCopy.RowCount)); + var lu = Math.Max(nct, nrt); + for (l = 0; l < lu; l++) + { + lp1 = l + 1; + if (l < nct) + { + // Compute the transformation for the l-th column and place the l-th diagonal in VectorS[l]. + var xnorm = Dnrm2Column(matrixCopy, matrixCopy.RowCount, l, l); + VectorS[l] = xnorm; + if (VectorS[l] != 0.0) + { + if (matrixCopy.At(l, l) != 0.0) + { + VectorS[l] = Dsign(VectorS[l], matrixCopy.At(l, l)); + } + + DscalColumn(matrixCopy, matrixCopy.RowCount, l, l, 1.0 / VectorS[l]); + matrixCopy.At(l, l, (1.0 + matrixCopy.At(l, l))); + } + + VectorS[l] = -VectorS[l]; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (l < nct) + { + if (VectorS[l] != 0.0) + { + // Apply the transformation. + t = -Ddot(matrixCopy, matrixCopy.RowCount, l, j, l) / matrixCopy.At(l, l); + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (t * matrixCopy.At(ii, l))); + } + } + } + + // Place the l-th row of matrixCopy into e for the + // Subsequent calculation of the row transformation. + e[j] = matrixCopy.At(l, j); + } + + if (ComputeVectors && l < nct) + { + // Place the transformation in u for subsequent back multiplication. + for (i = l; i < matrixCopy.RowCount; i++) + { + MatrixU.At(i, l, matrixCopy.At(i, l)); + } + } + + if (l >= nrt) + { + continue; + } + + // Compute the l-th row transformation and place the l-th super-diagonal in e(l). + var enorm = Dnrm2Vector(e, lp1); + e[l] = enorm; + if (e[l] != 0.0) + { + if (e[lp1] != 0.0) + { + e[l] = Dsign(e[l], e[lp1]); + } + + DscalVector(e, lp1, 1.0 / e[l]); + e[lp1] = 1.0 + e[lp1]; + } + + e[l] = -e[l]; + if (lp1 < matrixCopy.RowCount && e[l] != 0.0) + { + // Apply the transformation. + for (i = lp1; i < matrixCopy.RowCount; i++) + { + work[i] = 0.0; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + work[ii] += e[j] * matrixCopy.At(ii, j); + } + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + var ww = -e[j] / e[lp1]; + for (var ii = lp1; ii < matrixCopy.RowCount; ii++) + { + matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (ww * work[ii])); + } + } + } + + if (ComputeVectors) + { + // Place the transformation in v for subsequent back multiplication. + for (i = lp1; i < matrixCopy.ColumnCount; i++) + { + MatrixVT.At(i, l, e[i]); + } + } + } + + // Set up the final bidiagonal matrixCopy or order m. + var m = Math.Min(matrixCopy.ColumnCount, matrixCopy.RowCount + 1); + var nctp1 = nct + 1; + var nrtp1 = nrt + 1; + if (nct < matrixCopy.ColumnCount) + { + VectorS[nctp1 - 1] = matrixCopy.At((nctp1 - 1), (nctp1 - 1)); + } + + if (matrixCopy.RowCount < m) + { + VectorS[m - 1] = 0.0; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = matrixCopy.At((nrtp1 - 1), (m - 1)); + } + + e[m - 1] = 0.0; + + // If required, generate u. + if (ComputeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + MatrixU.At(i, j, 0.0); + } + + MatrixU.At(j, j, 1.0); + } + + for (l = nct - 1; l >= 0; l--) + { + if (VectorS[l] != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = -Ddot(MatrixU, matrixCopy.RowCount, l, j, l) / MatrixU.At(l, l); + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + MatrixU.At(ii, j, MatrixU.At(ii, j) + (t * MatrixU.At(ii, l))); + } + } + + DscalColumn(MatrixU, matrixCopy.RowCount, l, l, -1.0); + MatrixU.At(l, l, 1.0 + MatrixU.At(l, l)); + for (i = 0; i < l; i++) + { + MatrixU.At(i, l, 0.0); + } + } + else + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + MatrixU.At(i, l, 0.0); + } + + MatrixU.At(l, l, 1.0); + } + } + } + + // If it is required, generate v. + if (ComputeVectors) + { + for (l = matrixCopy.ColumnCount - 1; l >= 0; l--) + { + lp1 = l + 1; + if (l < nrt) + { + if (e[l] != 0.0) + { + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + t = -Ddot(MatrixVT, matrixCopy.ColumnCount, l, j, lp1) / MatrixVT.At(lp1, l); + for (var ii = l; ii < matrixCopy.ColumnCount; ii++) + { + MatrixVT.At(ii, j, MatrixVT.At(ii, j) + (t * MatrixVT.At(ii, l))); + } + } + } + } + + for (i = 0; i < matrixCopy.ColumnCount; i++) + { + MatrixVT.At(i, l, 0.0); + } + + MatrixVT.At(l, l, 1.0); + } + } + + // Transform s and e so that they are double . + for (i = 0; i < m; i++) + { + double r; + if (VectorS[i] != 0.0) + { + t = VectorS[i]; + r = VectorS[i] / t; + VectorS[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (ComputeVectors) + { + DscalColumn(MatrixU, matrixCopy.RowCount, i, 0, r); + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i] != 0.0) + { + t = e[i]; + r = t / e[i]; + e[i] = t; + VectorS[i + 1] = VectorS[i + 1] * r; + if (ComputeVectors) + { + DscalColumn(MatrixVT, matrixCopy.ColumnCount, i + 1, 0, r); + } + } + } + + // Main iteration loop for the singular values. + var mn = m; + var iter = 0; + + while (m > 0) + { + // Quit if all the singular values have been found. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= Maxiter) + { + throw new ArgumentException(Resources.ConvergenceFailed); + } + + // This section of the program inspects for negligible elements in the s and e arrays. On + // completion the variables kase and l are set as follows. + // Kase = 1 if VectorS[m] and e[l-1] are negligible and l < m + // Kase = 2 if VectorS[l] is negligible and l < m + // Kase = 3 if e[l-1] is negligible, l < m, and VectorS[l, ..., VectorS[m] are not negligible (qr step). + // Лase = 4 if e[m-1] is negligible (convergence). + double ztest; + double test; + for (l = m - 2; l >= 0; l--) + { + test = Math.Abs(VectorS[l]) + Math.Abs(VectorS[l + 1]); + ztest = test + Math.Abs(e[l]); + if (ztest.AlmostEqualInDecimalPlaces(test, 15)) + { + e[l] = 0.0; + break; + } + } + + int kase; + if (l == m - 2) + { + kase = 4; + } + else + { + int ls; + for (ls = m - 1; ls > l; ls--) + { + test = 0.0; + if (ls != m - 1) + { + test = test + Math.Abs(e[ls]); + } + + if (ls != l + 1) + { + test = test + Math.Abs(e[ls - 1]); + } + + ztest = test + Math.Abs(VectorS[ls]); + if (ztest.AlmostEqualInDecimalPlaces(test, 15)) + { + VectorS[ls] = 0.0; + break; + } + } + + if (ls == l) + { + kase = 3; + } + else if (ls == m - 1) + { + kase = 1; + } + else + { + kase = 2; + l = ls; + } + } + + l = l + 1; + + // Perform the task indicated by kase. + int k; + double f; + switch (kase) + { + // Deflate negligible VectorS[m]. + case 1: + f = e[m - 2]; + e[m - 2] = 0.0; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = VectorS[k]; + Drotg(ref t1, ref f, ref cs, ref sn); + VectorS[k] = t1; + if (k != l) + { + f = -sn * e[k - 1]; + e[k - 1] = cs * e[k - 1]; + } + + if (ComputeVectors) + { + Drot(MatrixVT, matrixCopy.ColumnCount, k, m - 1, cs, sn); + } + } + + break; + + // Split at negligible VectorS[l]. + case 2: + f = e[l - 1]; + e[l - 1] = 0.0; + for (k = l; k < m; k++) + { + t1 = VectorS[k]; + Drotg(ref t1, ref f, ref cs, ref sn); + VectorS[k] = t1; + f = -sn * e[k]; + e[k] = cs * e[k]; + if (ComputeVectors) + { + Drot(MatrixU, matrixCopy.RowCount, k, l - 1, cs, sn); + } + } + + break; + + // Perform one qr step. + case 3: + // Calculate the shift. + var scale = 0.0; + scale = Math.Max(scale, Math.Abs(VectorS[m - 1])); + scale = Math.Max(scale, Math.Abs(VectorS[m - 2])); + scale = Math.Max(scale, Math.Abs(e[m - 2])); + scale = Math.Max(scale, Math.Abs(VectorS[l])); + scale = Math.Max(scale, Math.Abs(e[l])); + var sm = VectorS[m - 1] / scale; + var smm1 = VectorS[m - 2] / scale; + var emm1 = e[m - 2] / scale; + var sl = VectorS[l] / scale; + var el = e[l] / scale; + var b = (((smm1 + sm) * (smm1 - sm)) + (emm1 * emm1)) / 2.0; + var c = (sm * emm1) * (sm * emm1); + var shift = 0.0; + if (b != 0.0 || c != 0.0) + { + shift = Math.Sqrt((b * b) + c); + if (b < 0.0) + { + shift = -shift; + } + + shift = c / (b + shift); + } + + f = ((sl + sm) * (sl - sm)) + shift; + var g = sl * el; + + // Chase zeros. + for (k = l; k < m - 1; k++) + { + Drotg(ref f, ref g, ref cs, ref sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * VectorS[k]) + (sn * e[k]); + e[k] = (cs * e[k]) - (sn * VectorS[k]); + g = sn * VectorS[k + 1]; + VectorS[k + 1] = cs * VectorS[k + 1]; + if (ComputeVectors) + { + Drot(MatrixVT, matrixCopy.ColumnCount, k, k + 1, cs, sn); + } + + Drotg(ref f, ref g, ref cs, ref sn); + VectorS[k] = f; + f = (cs * e[k]) + (sn * VectorS[k + 1]); + VectorS[k + 1] = (-sn * e[k]) + (cs * VectorS[k + 1]); + g = sn * e[k + 1]; + e[k + 1] = cs * e[k + 1]; + if (ComputeVectors && k < matrixCopy.RowCount) + { + Drot(MatrixU, matrixCopy.RowCount, k, k + 1, cs, sn); + } + } + + e[m - 2] = f; + iter = iter + 1; + break; + + // Convergence. + case 4: + // Make the singular value positive + if (VectorS[l] < 0.0) + { + VectorS[l] = -VectorS[l]; + if (ComputeVectors) + { + DscalColumn(MatrixVT, matrixCopy.ColumnCount, l, 0, -1.0); + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (VectorS[l] >= VectorS[l + 1]) + { + break; + } + + t = VectorS[l]; + VectorS[l] = VectorS[l + 1]; + VectorS[l + 1] = t; + if (ComputeVectors && l < matrixCopy.ColumnCount) + { + Dswap(MatrixVT, matrixCopy.ColumnCount, l, l + 1); + } + + if (ComputeVectors && l < matrixCopy.RowCount) + { + Dswap(MatrixU, matrixCopy.RowCount, l, l + 1); + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (ComputeVectors) + { + MatrixVT = MatrixVT.Transpose(); + } + + // Adjust the size of s if rows < columns. We are using ported copy of linpack's svd code and it uses + // a singular vector of length mRows+1 when mRows < mColumns. The last element is not used and needs to be removed. + // we should port lapack's svd routine to remove this problem. + if (matrixCopy.RowCount < matrixCopy.ColumnCount) + { + nm--; + var tmp = matrixCopy.CreateVector(nm); + for (i = 0; i < nm; i++) + { + tmp[i] = VectorS[i]; + } + + VectorS = tmp; + } + } + + /// + /// Calculates absolute value of multiplied on signum function of + /// + /// Double value z1 + /// Double value z2 + /// Result multiplication of signum function and absolute value + private static double Dsign(double z1, double z2) + { + return Math.Abs(z1) * (z2 / Math.Abs(z2)); + } + + /// + /// Swap column and + /// + /// Source matrix + /// The number of rows in + /// Column A index to swap + /// Column B index to swap + private static void Dswap(Matrix a, int rowCount, int columnA, int columnB) + { + for (var i = 0; i < rowCount; i++) + { + var z = a.At(i, columnA); + a.At(i, columnA, a.At(i, columnB)); + a.At(i, columnB, z); + } + } + + /// + /// Scale column by starting from row + /// + /// Source matrix + /// The number of rows in + /// Column to scale + /// Row to scale from + /// Scale value + private static void DscalColumn(Matrix a, int rowCount, int column, int rowStart, double z) + { + for (var i = rowStart; i < rowCount; i++) + { + a.At(i, column, a.At(i, column) * z); + } + } + + /// + /// Scale vector by starting from index + /// + /// Source vector + /// Row to scale from + /// Scale value + private static void DscalVector(double[] a, int start, double z) + { + for (var i = start; i < a.Length; i++) + { + a[i] = a[i] * z; + } + } + + /// + /// Given the Cartesian coordinates (da, db) of a point p, these fucntion return the parameters da, db, c, and s + /// associated with the Givens rotation that zeros the y-coordinate of the point. + /// + /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation + /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation + /// Contains the parameter c associated with the Givens rotation + /// Contains the parameter s associated with the Givens rotation + /// This is equivalent to the DROTG LAPACK routine. + private static void Drotg(ref double da, ref double db, ref double c, ref double s) + { + double r, z; + + var roe = db; + var absda = Math.Abs(da); + var absdb = Math.Abs(db); + if (absda > absdb) + { + roe = da; + } + + var scale = absda + absdb; + if (scale == 0.0) + { + c = 1.0; + s = 0.0; + r = 0.0; + z = 0.0; + } + else + { + var sda = da / scale; + var sdb = db / scale; + r = scale * Math.Sqrt((sda * sda) + (sdb * sdb)); + if (roe < 0.0) + { + r = -r; + } + + c = da / r; + s = db / r; + z = 1.0; + if (absda > absdb) + { + z = s; + } + + if (absdb >= absda && c != 0.0) + { + z = 1.0 / c; + } + } + + da = r; + db = z; + } + + /// dded + /// Calculate Norm 2 of the column in matrix starting from row + /// + /// Source matrix + /// The number of rows in + /// Column index + /// Start row index + /// Norm2 (Euclidean norm) of trhe column + private static double Dnrm2Column(Matrix a, int rowCount, int column, int rowStart) + { + double s = 0; + for (var i = rowStart; i < rowCount; i++) + { + s += a.At(i, column) * a.At(i, column); + } + + return Math.Sqrt(s); + } + + /// + /// Calculate Norm 2 of the vector starting from index + /// + /// Source vector + /// Start index + /// Norm2 (Euclidean norm) of the vector + private static double Dnrm2Vector(double[] a, int rowStart) + { + double s = 0; + for (var i = rowStart; i < a.Length; i++) + { + s += a[i] * a[i]; + } + + return Math.Sqrt(s); + } + + /// + /// Calculate dot product of and + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Starting row index + /// Dot product value + private static double Ddot(Matrix a, int rowCount, int columnA, int columnB, int rowStart) + { + var z = 0.0; + for (var i = rowStart; i < rowCount; i++) + { + z += a.At(i, columnB) * a.At(i, columnA); + } + + return z; + } + + /// + /// Performs rotation of points in the plane. Given two vectors x and y , + /// each vector element of these vectors is replaced as follows: x(i) = c*x(i) + s*y(i); y(i) = c*y(i) - s*x(i) + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Scalar "c" value + /// Scalar "s" value + private static void Drot(Matrix a, int rowCount, int columnA, int columnB, double c, double s) + { + for (var i = 0; i < rowCount; i++) + { + var z = (c * a.At(i, columnA)) + (s * a.At(i, columnB)); + var tmp = (c * a.At(i, columnB)) - (s * a.At(i, columnA)); + a.At(i, columnB, tmp); + a.At(i, columnA, z); + } + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (!ComputeVectors) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (MatrixU.RowCount != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (MatrixVT.ColumnCount != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + var mn = Math.Min(MatrixU.RowCount, MatrixVT.ColumnCount); + var bn = input.ColumnCount; + + var tmp = new double[MatrixVT.ColumnCount]; + + for (var k = 0; k < bn; k++) + { + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + double value = 0; + if (j < mn) + { + for (var i = 0; i < MatrixU.RowCount; i++) + { + value += MatrixU.At(i, j) * input.At(i, k); + } + + value /= VectorS[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + double value = 0; + for (var i = 0; i < MatrixVT.ColumnCount; i++) + { + value += MatrixVT.At(i, j) * tmp[i]; + } + + result[j, k] = value; + } + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (!ComputeVectors) + { + throw new InvalidOperationException(Resources.SingularVectorsNotComputed); + } + + // Ax=b where A is an m x n matrix + // Check that b is a column vector with m entries + if (MatrixU.RowCount != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (MatrixVT.ColumnCount != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var mn = Math.Min(MatrixU.RowCount, MatrixVT.ColumnCount); + var tmp = new double[MatrixVT.ColumnCount]; + double value; + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + value = 0; + if (j < mn) + { + for (var i = 0; i < MatrixU.RowCount; i++) + { + value += MatrixU.At(i, j) * input[i]; + } + + value /= VectorS[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + value = 0; + for (int i = 0; i < MatrixVT.ColumnCount; i++) + { + value += MatrixVT.At(i, j) * tmp[i]; + } + + result[j] = value; + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/UserQR.cs b/src/Numerics/LinearAlgebra/Double/Factorization/UserQR.cs index 2595fadb..01581f91 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/UserQR.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/UserQR.cs @@ -216,9 +216,8 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization var inputCopy = input.Clone(); // Compute Y = transpose(Q)*B - var bn = inputCopy.ColumnCount; var column = new double[MatrixR.RowCount]; - for (var j = 0; j < bn; j++) + for (var j = 0; j < input.ColumnCount; j++) { for (var k = 0; k < MatrixR.RowCount; k++) { @@ -240,14 +239,14 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization // Solve R*X = Y; for (var k = MatrixR.ColumnCount - 1; k >= 0; k--) { - for (var j = 0; j < bn; j++) + for (var j = 0; j < input.ColumnCount; j++) { inputCopy.At(k, j, inputCopy.At(k, j) / MatrixR.At(k, k)); } for (var i = 0; i < k; i++) { - for (var j = 0; j < bn; j++) + for (var j = 0; j < input.ColumnCount; j++) { inputCopy.At(i, j, inputCopy.At(i, j) - (inputCopy.At(k, j) * MatrixR.At(i, k))); } diff --git a/src/Numerics/LinearAlgebra/Double/Matrix.Arithmetic.cs b/src/Numerics/LinearAlgebra/Double/Matrix.Arithmetic.cs index d67e700d..7aaec76c 100644 --- a/src/Numerics/LinearAlgebra/Double/Matrix.Arithmetic.cs +++ b/src/Numerics/LinearAlgebra/Double/Matrix.Arithmetic.cs @@ -163,7 +163,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double } /// - /// Multiplies this matrix with a vector and places the results into the result matrix. + /// Multiplies this matrix with a vector and places the results into the result vactor. /// /// The vector to multiply with. /// The result of the multiplication. @@ -368,6 +368,88 @@ namespace MathNet.Numerics.LinearAlgebra.Double return result; } + /// + /// Multiplies this matrix with transpose of another matrix and places the results into the result matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If the other matrix is . + /// If the result matrix is . + /// If this.Columns != other.ColumnCount. + /// If the result matrix's dimensions are not the this.RowCount x other.RowCount. + public virtual void TransposeAndMultiply(Matrix other, Matrix result) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (ColumnCount != other.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if ((result.RowCount != RowCount) || (result.ColumnCount != other.RowCount)) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = result.CreateMatrix(result.RowCount, result.ColumnCount); + TransposeAndMultiply(other, tmp); + tmp.CopyTo(result); + } + else + { + CommonParallel.For( + 0, + RowCount, + j => + { + for (var i = 0; i < RowCount; i++) + { + double s = 0; + for (var l = 0; l < ColumnCount; l++) + { + s += At(i, l) * other.At(j, l); + } + + result.At(i, j, s + result.At(i, j)); + } + }); + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.ColumnCount. + /// If the other matrix is . + /// The result of the multiplication. + public virtual Matrix TransposeAndMultiply(Matrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (ColumnCount != other.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var result = CreateMatrix(RowCount, other.RowCount); + TransposeAndMultiply(other, result); + return result; + } + /// /// Negates each element of this matrix. /// @@ -971,18 +1053,33 @@ namespace MathNet.Numerics.LinearAlgebra.Double } var ret = Clone(); + + // BUG: Seems that commented-out implementation is wrong. + // CommonParallel.For( + // 0, + // ColumnCount, + // j => + // { + // var rowj = Row(j); + // var norm = rowj.Norm(p); + // for (var i = 0; i < RowCount; i++) + // { + // ret[i, j] = rowj[j] / norm; + // } + // }); CommonParallel.For( - 0, - ColumnCount, - j => + 0, + RowCount, + i => { - var rowj = Row(j); - var norm = rowj.Norm(p); - for (var i = 0; i < RowCount; i++) + var rowi = Row(i); + var norm = rowi.Norm(p); + for (var j = 0; j < ColumnCount; j++) { - ret[i, j] = rowj[j] / norm; + ret[i, j] = rowi[j] / norm; } }); + return ret; } } diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolver.cs b/src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolver.cs new file mode 100644 index 00000000..af2573f4 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolver.cs @@ -0,0 +1,96 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers +{ + using Status; + + /// + /// Defines the interface for classes that solve the matrix equation Ax = b in + /// an iterative manner. + /// + public interface IIterativeSolver + { + /// + /// Stops the solve process. + /// + /// + /// Note that it may take an indetermined amount of time for the solver to actually stop the process. + /// + void StopSolve(); + + /// + /// Sets the that will be used to track the iterative process. + /// + /// The iterator. + void SetIterator(IIterator iterator); + + /// + /// Gets the status of the iteration once the calculation is finished. + /// + ICalculationStatus IterationResult { get; } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b. + /// The result vector, x. + Vector Solve(Matrix matrix, Vector vector); + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + void Solve(Matrix matrix, Vector input, Vector result); + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X. + Matrix Solve(Matrix matrix, Matrix input); + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X + void Solve(Matrix matrix, Matrix input, Matrix result); + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolverSetup.cs b/src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolverSetup.cs new file mode 100644 index 00000000..86ec59f4 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/IIterativeSolverSetup.cs @@ -0,0 +1,71 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers +{ + using System; + + /// + /// Defines the interface for objects that can create an iterative solver with + /// specific settings. This interface is used to pass iterative solver creation + /// setup information around. + /// + public interface IIterativeSolverSetup + { + /// + /// Gets the type of the solver that will be created by this setup object. + /// + Type SolverType { get; } + + /// + /// Gets type of preconditioner, if any, that will be created by this setup object. + /// + Type PreconditionerType { get; } + + /// + /// Creates a fully functional iterative solver with the default settings + /// given by this setup. + /// + /// A new . + IIterativeSolver CreateNew(); + + /// + /// Gets the relative speed of the solver. + /// + /// Returns a value between 0 and 1, inclusive. + double SolutionSpeed { get; } + + /// + /// Gets the relative reliability of the solver. + /// + /// Returns a value between 0 and 1 inclusive. + double Reliability { get; } + } +} \ No newline at end of file diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/IIterator.cs b/src/Numerics/LinearAlgebra/Double/Solvers/IIterator.cs new file mode 100644 index 00000000..4ccfaa3e --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/IIterator.cs @@ -0,0 +1,108 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers +{ + using System; + using Status; + using StopCriterium; + + /// + /// Defines the base interface for iterators that help control an iterative calculation. + /// + public interface IIterator +#if !SILVERLIGHT + : ICloneable +#endif + { + /// + /// Adds an to the internal collection of stop-criteria. Only a + /// single stop criterium of each type can be stored. + /// + /// The stop criterium to add. + /// Thrown if is . + /// Thrown if is of the same type as an already stored criterium. + void Add(IIterationStopCriterium stopCriterium); + + /// + /// Removes the from the internal collection. + /// + /// The stop criterium that must be removed. + void Remove(IIterationStopCriterium stopCriterium); + + /// + /// Indicates if the specific stop criterium is stored by the . + /// + /// The stop criterium. + /// true if the contains the stop criterium; otherwise false. + bool Contains(IIterationStopCriterium stopCriterium); + + /// + /// Indicates to the iterator that the iterative process has been cancelled. + /// + /// Does not reset the stop-criteria. + void IterationCancelled(); + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Status is set to Status field of current object. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual iterators may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector); + + /// + /// Gets the current calculation status. + /// + /// is not a legal value. Status should be set in implementation.. + ICalculationStatus Status { get; } + + /// + /// Resets the IIterator to the pre-calculation state. + /// + /// + /// Note to implementers: Invoking this method should not clear the user defined + /// property values, only the state that is used to track the progress of the + /// calculation. + /// + void ResetToPrecalculationState(); + +#if SILVERLIGHT + IIterator Clone(); +#endif + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/BiCgStab.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/BiCgStab.cs new file mode 100644 index 00000000..c3279cbc --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/BiCgStab.cs @@ -0,0 +1,524 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Iterative +{ + using System; + using Preconditioners; + using Properties; + using Status; + + /// + /// A Bi-Conjugate Gradient stabilized iterative matrix solver. + /// + /// + /// + /// The Bi-Conjugate Gradient Stabilized (BiCGStab) solver is an 'improvement' + /// of the standard Conjugate Gradient (CG) solver. Unlike the CG solver the + /// BiCGStab can be used on non-symmetric matrices.
+ /// Note that much of the success of the solver depends on the selection of the + /// proper preconditioner. + ///
+ /// + /// The Bi-CGSTAB algorithm was taken from:
+ /// Templates for the solution of linear systems: Building blocks + /// for iterative methods + ///
+ /// Richard Barrett, Michael Berry, Tony F. Chan, James Demmel, + /// June M. Donato, Jack Dongarra, Victor Eijkhout, Roldan Pozo, + /// Charles Romine and Henk van der Vorst + ///
+ /// Url: http://www.netlib.org/templates/Templates.html + ///
+ /// Algorithm is described in Chapter 2, section 2.3.8, page 27 + ///
+ /// + /// The example code below provides an indication of the possible use of the + /// solver. + /// + ///
+ public sealed class BiCgStab : IIterativeSolver + { + /// + /// The status used if there is no status, i.e. the solver hasn't run yet and there is no + /// iterator. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The preconditioner that will be used. Can be set to , in which case the default + /// pre-conditioner will be used. + /// + private IPreConditioner _preconditioner; + + /// + /// The iterative process controller. + /// + private IIterator _iterator; + + /// + /// Indicates if the user has stopped the solver. + /// + private bool _hasBeenStopped; + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings and a default preconditioner. + /// + public BiCgStab() : this(null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// When using this constructor the solver will use a default preconditioner. + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to monitor the iterative process. + public BiCgStab(IIterator iterator) : this(null, iterator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings. + /// + /// The that will be used to precondition the matrix equation. + public BiCgStab(IPreConditioner preconditioner) : this(preconditioner, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to precondition the matrix equation. + /// The that will be used to monitor the iterative process. + public BiCgStab(IPreConditioner preconditioner, IIterator iterator) + { + _iterator = iterator; + _preconditioner = preconditioner; + } + + /// + /// Sets the that will be used to precondition the iterative process. + /// + /// The preconditioner. + public void SetPreconditioner(IPreConditioner preconditioner) + { + _preconditioner = preconditioner; + } + + /// + /// Sets the that will be used to track the iterative process. + /// + /// The iterator. + public void SetIterator(IIterator iterator) + { + _iterator = iterator; + } + + /// + /// Gets the status of the iteration once the calculation is finished. + /// + public ICalculationStatus IterationResult + { + get + { + return (_iterator != null) ? _iterator.Status : DefaultStatus; + } + } + + /// + /// Stops the solve process. + /// + /// + /// Note that it may take an indetermined amount of time for the solver to actually stop the process. + /// + public void StopSolve() + { + _hasBeenStopped = true; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient , A. + /// The solution , b. + /// The result , x. + public Vector Solve(Matrix matrix, Vector vector) + { + if (vector == null) + { + throw new ArgumentNullException(); + } + + Vector result = new DenseVector(matrix.RowCount); + Solve(matrix, vector, result); + return result; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient , A. + /// The solution , b. + /// The result , x. + public void Solve(Matrix matrix, Vector input, Vector result) + { + // If we were stopped before, we are no longer + // We're doing this at the start of the method to ensure + // that we can use these fields immediately. + _hasBeenStopped = false; + + // Parameters checks + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Initialize the solver fields + // Set the convergence monitor + if (_iterator == null) + { + _iterator = Iterator.CreateDefault(); + } + + if (_preconditioner == null) + { + _preconditioner = new UnitPreconditioner(); + } + + _preconditioner.Initialize(matrix); + + // Compute r_0 = b - Ax_0 for some initial guess x_0 + // In this case we take x_0 = vector + // This is basically a SAXPY so it could be made a lot faster + Vector residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, result, input); + + // Choose r~ (for example, r~ = r_0) + var tempResiduals = residuals.Clone(); + var temp = result.Clone(); + + // create five temporary vectors needed to hold temporary + // coefficients. All vectors are mangled in each iteration. + // These are defined here to prevent stressing the garbage collector + Vector vecP = new DenseVector(residuals.Count); + Vector vecPdash = new DenseVector(residuals.Count); + Vector nu = new DenseVector(residuals.Count); + Vector vecS = new DenseVector(residuals.Count); + Vector vecSdash = new DenseVector(residuals.Count); + Vector t = new DenseVector(residuals.Count); + + Vector mult = new DenseVector(residuals.Count); + + // create some temporary double variables that are needed + // to hold values in between iterations + double currentRho = 0; + double alpha = 0; + double omega = 0; + + var iterationNumber = 0; + while (ShouldContinue(iterationNumber, result, input, residuals)) + { + // rho_(i-1) = r~^T r_(i-1) // dotproduct r~ and r_(i-1) + var oldRho = currentRho; + currentRho = tempResiduals.DotProduct(residuals); + + // if (rho_(i-1) == 0) // METHOD FAILS + // If rho is only 1 ULP from zero then we fail. + if (currentRho.AlmostEqual(0, 1)) + { + // Rho-type breakdown + throw new Exception("Iterative solver experience a numerical break down"); + } + + if (iterationNumber != 0) + { + // beta_(i-1) = (rho_(i-1)/rho_(i-2))(alpha_(i-1)/omega(i-1)) + var beta = (currentRho / oldRho) * (alpha / omega); + + // p_i = r_(i-1) + beta_(i-1)(p_(i-1) - omega_(i-1) * nu_(i-1)) + nu.Multiply(-omega, mult); + vecP.Add(mult); + + vecP.Multiply(beta); + vecP.Add(residuals); + } + else + { + // p_i = r_(i-1) + residuals.CopyTo(vecP); + } + + // SOLVE Mp~ = p_i // M = preconditioner + _preconditioner.Approximate(vecP, vecPdash); + + // nu_i = Ap~ + matrix.Multiply(vecPdash, nu); + + // alpha_i = rho_(i-1)/ (r~^T nu_i) = rho / dotproduct(r~ and nu_i) + alpha = currentRho * 1 / tempResiduals.DotProduct(nu); + + // s = r_(i-1) - alpha_i nu_i + nu.Multiply(-alpha, mult); + residuals.Add(mult, vecS); + + // Check if we're converged. If so then stop. Otherwise continue; + // Calculate the temporary result. + // Be careful not to change any of the temp vectors, except for + // temp. Others will be used in the calculation later on. + // x_i = x_(i-1) + alpha_i * p^_i + s^_i + vecPdash.Multiply(alpha, temp); + temp.Add(vecSdash); + temp.Add(result); + + // Check convergence and stop if we are converged. + if (!ShouldContinue(iterationNumber, temp, input, vecS)) + { + temp.CopyTo(result); + + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, result, input); + + // Now recheck the convergence + if (!ShouldContinue(iterationNumber, result, input, residuals)) + { + // We're all good now. + return; + } + + // Continue the calculation + iterationNumber++; + continue; + } + + // SOLVE Ms~ = s + _preconditioner.Approximate(vecS, vecSdash); + + // temp = As~ + matrix.Multiply(vecSdash, t); + + // omega_i = temp^T s / temp^T temp + omega = t.DotProduct(vecS) / t.DotProduct(t); + + // x_i = x_(i-1) + alpha_i p^ + omega_i s^ + vecSdash.Multiply(omega, mult); + result.Add(mult); + + vecPdash.Multiply(alpha, mult); + result.Add(mult); + + t.Multiply(-omega, residuals); + residuals.Add(vecS); + + // for continuation it is necessary that omega_i != 0.0 + // If omega is only 1 ULP from zero then we fail. + if (omega.AlmostEqual(0, 1)) + { + // Omega-type breakdown + throw new Exception("Iterative solver experience a numerical break down"); + } + + if (!ShouldContinue(iterationNumber, result, input, residuals)) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + // The residual calculation based on omega_i * s can be off by a factor 10. So here + // we calculate the real residual (which can be expensive) but we only do it if we're + // sufficiently close to the finish. + CalculateTrueResidual(matrix, residuals, result, input); + } + + iterationNumber++; + } + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + private static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + + // Do not use residual = residual.Negate() because it creates another object + residual.Multiply(-1); + + // residual + b + residual.Add(b); + } + + /// + /// Determine if calculation should continue + /// + /// Number of iterations passed + /// Result . + /// Source . + /// Residual . + /// true if continue, otherwise false + private bool ShouldContinue(int iterationNumber, Vector result, Vector source, Vector residuals) + { + if (_hasBeenStopped) + { + _iterator.IterationCancelled(); + return true; + } + + _iterator.DetermineStatus(iterationNumber, result, source, residuals); + var status = _iterator.Status; + + // We stop if either: + // - the user has stopped the calculation + // - the calculation needs to be stopped from a numerical point of view (divergence, convergence etc.) + return (!status.TerminatesCalculation) && (!_hasBeenStopped); + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient , A. + /// The solution , B. + /// The result , X. + public Matrix Solve(Matrix matrix, Matrix input) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var result = matrix.CreateMatrix(input.RowCount, input.ColumnCount); + Solve(matrix, input, result); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient , A. + /// The solution , B. + /// The result , X + public void Solve(Matrix matrix, Matrix input, Matrix result) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (matrix.RowCount != input.RowCount || input.RowCount != result.RowCount || input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + for (var column = 0; column < input.ColumnCount; column++) + { + var solution = Solve(matrix, input.Column(column)); + foreach (var element in solution.GetIndexedEnumerator()) + { + result.At(element.Key, column, element.Value); + } + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/CompositeSolver.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/CompositeSolver.cs new file mode 100644 index 00000000..378e571e --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/CompositeSolver.cs @@ -0,0 +1,627 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Iterative +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using Properties; + using Status; + + /// + /// A composite matrix solver. The actual solver is made by a sequence of + /// matrix solvers. + /// + /// + /// + /// Solver based on:
+ /// Faster PDE-based simulations using robust composite linear solvers
+ /// S. Bhowmicka, P. Raghavan a,*, L. McInnes b, B. Norris
+ /// Future Generation Computer Systems, Vol 20, 2004, pp 373–387
+ ///
+ /// + /// Note that if an iterator is passed to this solver it will be used for all the sub-solvers. + /// + ///
+ public sealed class CompositeSolver : IIterativeSolver + { + #region Internal class - DoubleComparer + /// + /// An IComparer used to compare double precision floating points. + /// + /// NOTE: The instance of this class is used only in . If C# suppports interface inheritence + /// NOTE: and methods in anonymous types, then this class should be deleted and anonymous type implemented with IComaprer support + /// NOTE: in constructor + public sealed class DoubleComparer : IComparer + { + /// + /// Compares two double values based on the selected comparison method. + /// + /// The first double to compare. + /// The second double to compare. + /// + /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return + /// value has the following meanings: + /// Value Meaning Less than zero This object is less than the other parameter. + /// Zero This object is equal to other. + /// Greater than zero This object is greater than other. + /// + public int Compare(double x, double y) + { + return x.CompareTo(y, 1); + } + } + #endregion + + /// + /// The default status used if the solver is not running. + /// + private static readonly ICalculationStatus NonRunningStatus = new CalculationIndetermined(); + + /// + /// The default status used if the solver is running. + /// + private static readonly ICalculationStatus RunningStatus = new CalculationRunning(); + +#if SILVERLIGHT + private static readonly Dictionary> SolverSetups = new Dictionary>(); +#else + /// + /// The collection of iterative solver setups. Stored based on the + /// ratio between the relative speed and relative accuracy. + /// + private static readonly SortedList> SolverSetups = new SortedList>(new DoubleComparer()); +#endif + + #region Solver information loading methods + + /// + /// Loads all the available objects from the MathNet.Numerics assembly. + /// + public static void LoadSolverInformation() + { + LoadSolverInformation(new Type[0]); + } + + /// + /// Loads the available objects from the MathNet.Numerics assembly. + /// + /// The types that should not be loaded. + public static void LoadSolverInformation(Type[] typesToExclude) + { + LoadSolverInformationFromAssembly(Assembly.GetExecutingAssembly(), typesToExclude); + } + + /// + /// Loads the available objects from the assembly specified by the file location. + /// + /// The fully qualified path to the assembly. + public static void LoadSolverInformationFromAssembly(string assemblyLocation) + { + LoadSolverInformationFromAssembly(assemblyLocation, new Type[0]); + } + + /// + /// Loads the available objects from the assembly specified by the file location. + /// + /// The fully qualified path to the assembly. + /// The types that should not be loaded. + public static void LoadSolverInformationFromAssembly(string assemblyLocation, params Type[] typesToExclude) + { + if (assemblyLocation == null) + { + throw new ArgumentNullException("assemblyLocation"); + } + + if (assemblyLocation.Length == 0) + { + throw new ArgumentException(); + } + + if (!File.Exists(assemblyLocation)) + { + throw new FileNotFoundException(); + } + + // Get the assembly name + var assemblyFileName = Path.GetFileNameWithoutExtension(assemblyLocation); + + // Now load the assembly with an AssemblyName + var assemblyName = new AssemblyName(assemblyFileName); + var assembly = Assembly.Load(assemblyName); + + // Can't get this because we checked that the file exists. + // FileNotFoundException --> Can't get this because we checked that the file exists. + // FileLoadException + // BadImageFormatException + // Now we can load the solver information. + LoadSolverInformationFromAssembly(assembly, typesToExclude); + } + + /// + /// Loads the available objects from the assembly specified by the assembly name. + /// + /// The of the assembly that should be searched for setup objects. + public static void LoadSolverInformationFromAssembly(AssemblyName assemblyName) + { + LoadSolverInformationFromAssembly(assemblyName, new Type[0]); + } + + /// + /// Loads the available objects from the assembly specified by the assembly name. + /// + /// The of the assembly that should be searched for setup objects. + /// The types that should not be loaded. + public static void LoadSolverInformationFromAssembly(AssemblyName assemblyName, params Type[] typesToExclude) + { + if (assemblyName == null) + { + throw new ArgumentNullException("assemblyName"); + } + + var assembly = Assembly.Load(assemblyName); + + // May throw: + // ArgumentNullException --> Can't get this because we checked it. + // FileNotFoundException + // FileLoadException + // BadImageFormatException + // Now we can load the solver information. + LoadSolverInformationFromAssembly(assembly, typesToExclude); + } + + /// + /// Loads the available objects from the assembly specified by the type. + /// + /// The type in the assembly which should be searched for setup objects. + public static void LoadSolverInformationFromAssembly(Type typeInAssembly) + { + LoadSolverInformationFromAssembly(typeInAssembly, new Type[0]); + } + + /// + /// Loads the available objects from the assembly specified by the type. + /// + /// The type in the assembly which should be searched for setup objects. + /// The types that should not be loaded. + public static void LoadSolverInformationFromAssembly(Type typeInAssembly, params Type[] typesToExclude) + { + if (typeInAssembly == null) + { + throw new ArgumentNullException("typeInAssembly"); + } + + LoadSolverInformationFromAssembly(typeInAssembly.Assembly, typesToExclude); + } + + /// + /// Loads the available objects from the specified assembly. + /// + /// The assembly which will be searched for setup objects. + public static void LoadSolverInformationFromAssembly(Assembly assembly) + { + LoadSolverInformationFromAssembly(assembly, new Type[0]); + } + + /// + /// Loads the available objects from the specified assembly. + /// + /// The assembly which will be searched for setup objects. + /// The types that should not be loaded. + public static void LoadSolverInformationFromAssembly(Assembly assembly, params Type[] typesToExclude) + { + if (assembly == null) + { + throw new ArgumentNullException("Assembly"); + } + + if (typesToExclude == null) + { + throw new ArgumentNullException("typesToExclude"); + } + + var excludedTypes = new List(typesToExclude); + + // Load all the types in the assembly + // Find all the types that implement IIterativeSolverSetup + // Create an object of each of these types + // Get the type of the iterative solver that will be instantiated by the setup object + // Check if it's on the excluding list, if so throw the setup object away otherwise keep it. + var interfaceTypes = new List(); + foreach (var type in assembly.GetTypes().Where(type => (!type.IsAbstract && !type.IsEnum && !type.IsInterface && type.IsVisible))) + { + interfaceTypes.AddRange(type.GetInterfaces()); + if (!interfaceTypes.Any(match => typeof(IIterativeSolverSetup).IsAssignableFrom(match))) + { + continue; + } + + // See if we actually want this type of iterative solver + IIterativeSolverSetup setup; + try + { + // If something goes wrong we just ignore it and move on with the next type. + // There should probably be a log somewhere indicating that something went wrong? + setup = (IIterativeSolverSetup)Activator.CreateInstance(type); + } + catch (ArgumentException) + { + continue; + } + catch (NotSupportedException) + { + continue; + } + catch (TargetInvocationException) + { + continue; + } + catch (MethodAccessException) + { + continue; + } + catch (MissingMethodException) + { + continue; + } + catch (MemberAccessException) + { + continue; + } + catch (TypeLoadException) + { + continue; + } + + if (excludedTypes.Any(match => match.IsAssignableFrom(setup.SolverType) || + match.IsAssignableFrom(setup.PreconditionerType))) + { + continue; + } + + // Ok we want the solver, so store the object + var ratio = setup.SolutionSpeed / setup.Reliability; + if (!SolverSetups.ContainsKey(ratio)) + { + SolverSetups.Add(ratio, new List()); + } + + var list = SolverSetups[ratio]; + list.Add(setup); + } + } + + #endregion + + /// + /// The collection of solvers that will be used to + /// + private readonly List _solvers = new List(); + + /// + /// The status of the calculation. + /// + private ICalculationStatus _status = NonRunningStatus; + + /// + /// The iterator that is used to control the iteration process. + /// + private IIterator _iterator; + + /// + /// A flag indicating if the solver has been stopped or not. + /// + private bool _hasBeenStopped; + + /// + /// The solver that is currently running. Reference is used to be able to stop the + /// solver if the user cancels the solve process. + /// + private IIterativeSolver _currentSolver; + + /// + /// Initializes a new instance of the class with the default iterator. + /// + public CompositeSolver() : this(null) + { + } + + /// + /// Initializes a new instance of the class with the specified iterator. + /// + /// The iterator that will be used to control the iteration process. + public CompositeSolver(IIterator iterator) + { + _iterator = iterator; + } + + /// + /// Sets the IIterator that will be used to track the iterative process. + /// + /// The iterator. + public void SetIterator(IIterator iterator) + { + _iterator = iterator; + } + + /// + /// Gets the status of the iteration once the calculation is finished. + /// + public ICalculationStatus IterationResult + { + get + { + return _status; + } + } + + /// + /// Stops the solve process. + /// + /// + /// Note that it may take an indetermined amount of time for the solver to actually stop the process. + /// + public void StopSolve() + { + _hasBeenStopped = true; + if (_currentSolver != null) + { + _currentSolver.StopSolve(); + } + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b. + /// The result vector, x. + public Vector Solve(Matrix matrix, Vector vector) + { + if (vector == null) + { + throw new ArgumentNullException(); + } + + Vector result = new DenseVector(matrix.RowCount); + Solve(matrix, vector, result); + return result; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + public void Solve(Matrix matrix, Vector input, Vector result) + { + // If we were stopped before, we are no longer + // We're doing this at the start of the method to ensure + // that we can use these fields immediately. + _hasBeenStopped = false; + _currentSolver = null; + + // Error checks + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Initialize the solver fields + // Set the convergence monitor + if (_iterator == null) + { + _iterator = Iterator.CreateDefault(); + } + + // Load the solvers into our own internal data structure + // Once we have solvers we can always reuse them. + if (_solvers.Count == 0) + { + LoadSolvers(); + } + + // Create a copy of the solution and result vectors so we can use them + // later on + var internalInput = input.Clone(); + var internalResult = result.Clone(); + + foreach (var solver in _solvers.TakeWhile(solver => !_hasBeenStopped)) + { + // Store a reference to the solver so we can stop it. + _currentSolver = solver; + + try + { + // Reset the iterator and pass it to the solver + _iterator.ResetToPrecalculationState(); + solver.SetIterator(_iterator); + + // Start the solver + solver.Solve(matrix, internalInput, internalResult); + } + catch (Exception) + { + // The solver broke down. + // Log a message about this + // Switch to the next preconditioner. + // Reset the solution vector to the previous solution + input.CopyTo(internalInput); + _status = RunningStatus; + continue; + } + + // There was no fatal breakdown so check the status + if (_iterator.Status is CalculationConverged) + { + // We're done + break; + } + + // We're not done + // Either: + // - calculation finished without convergence + if (_iterator.Status is CalculationStoppedWithoutConvergence) + { + // Copy the internal result to the result vector and + // continue with the calculation. + internalInput.CopyTo(input); + } + else + { + // - calculation failed --> restart with the original vector + // - calculation diverged --> restart with the original vector + // - Some unknown status occurred --> To be safe restart. + input.CopyTo(internalInput); + } + } + + // Inside the loop we already copied the final results (if there are any) + // So no need to do that again. + + // Clean up + // No longer need the current solver + _currentSolver = null; + + // Set the final status + _status = _iterator.Status; + } + + /// + /// Load solvers + /// + private void LoadSolvers() + { + if (SolverSetups.Count == 0) + { + throw new Exception("IIterativeSolverSetup objects not found"); + } + +#if SILVERLIGHT + foreach (var setup in SolverSetups.OrderBy(solver => solver.Key, new DoubleComparer()).Select(pair => pair.Value).SelectMany(setups => setups)) +#else + foreach (var setup in SolverSetups.Select(pair => pair.Value).SelectMany(setups => setups)) +#endif + { + _solvers.Add(setup.CreateNew()); + } + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X. + public Matrix Solve(Matrix matrix, Matrix input) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var result = matrix.CreateMatrix(input.RowCount, input.ColumnCount); + Solve(matrix, input, result); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X + public void Solve(Matrix matrix, Matrix input, Matrix result) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (matrix.RowCount != input.RowCount || input.RowCount != result.RowCount || input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + for (var column = 0; column < input.ColumnCount; column++) + { + var solution = Solve(matrix, input.Column(column)); + foreach (var element in solution.GetIndexedEnumerator()) + { + result.At(element.Key, column, element.Value); + } + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/GpBiCg.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/GpBiCg.cs new file mode 100644 index 00000000..d981caf6 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/GpBiCg.cs @@ -0,0 +1,634 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Iterative +{ + using System; + using Preconditioners; + using Properties; + using Status; + + /// + /// A Generalized Product Bi-Conjugate Gradient iterative matrix solver. + /// + /// + /// + /// The Generalized Product Bi-Conjugate Gradient (GPBiCG) solver is an + /// alternative version of the Bi-Conjugate Gradient stabilized (CG) solver. + /// Unlike the CG solver the GPBiCG solver can be used on + /// non-symmetric matrices.
+ /// Note that much of the success of the solver depends on the selection of the + /// proper preconditioner. + ///
+ /// + /// The GPBiCG algorithm was taken from:
+ /// GPBiCG(m,l): A hybrid of BiCGSTAB and GPBiCG methods with + /// efficiency and robustness + ///
+ /// S. Fujino + ///
+ /// Applied Numerical Mathematics, Volume 41, 2002, pp 107 - 117 + ///
+ ///
+ /// + /// The example code below provides an indication of the possible use of the + /// solver. + /// + ///
+ public sealed class GpBiCg : IIterativeSolver + { + /// + /// The status used if there is no status, i.e. the solver hasn't run yet and there is no + /// iterator. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The preconditioner that will be used. Can be set to null, in which case the default + /// pre-conditioner will be used. + /// + private IPreConditioner _preconditioner; + + /// + /// The iterative process controller. + /// + private IIterator _iterator; + + /// + /// Indicates the number of BiCGStab steps should be taken + /// before switching. + /// + private int _numberOfBiCgStabSteps = 1; + + /// + /// Indicates the number of GPBiCG steps should be taken + /// before switching. + /// + private int _numberOfGpbiCgSteps = 4; + + /// + /// Indicates if the user has stopped the solver. + /// + private bool _hasBeenStopped; + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings and a default preconditioner. + /// + public GpBiCg() : this(null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// When using this constructor the solver will use a default preconditioner. + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to monitor the iterative process. + public GpBiCg(IIterator iterator) : this(null, iterator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings. + /// + /// The that will be used to precondition the matrix equation. + public GpBiCg(IPreConditioner preconditioner) : this(preconditioner, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to precondition the matrix equation. + /// The that will be used to monitor the iterative process. + public GpBiCg(IPreConditioner preconditioner, IIterator iterator) + { + _iterator = iterator; + _preconditioner = preconditioner; + } + + /// + /// Gets or sets the number of steps taken with the BiCgStab algorithm + /// before switching over to the GPBiCG algorithm. + /// + public int NumberOfBiCgStabSteps + { + get + { + return _numberOfBiCgStabSteps; + } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("value"); + } + + _numberOfBiCgStabSteps = value; + } + } + + /// + /// Gets or sets the number of steps taken with the GPBiCG algorithm + /// before switching over to the BiCgStab algorithm. + /// + public int NumberOfGpBiCgSteps + { + get + { + return _numberOfGpbiCgSteps; + } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("value"); + } + + _numberOfGpbiCgSteps = value; + } + } + + /// + /// Sets the that will be used to precondition the iterative process. + /// + /// The preconditioner. + public void SetPreconditioner(IPreConditioner preconditioner) + { + _preconditioner = preconditioner; + } + + /// + /// Sets the that will be used to track the iterative process. + /// + /// The iterator. + public void SetIterator(IIterator iterator) + { + _iterator = iterator; + } + + /// + /// Gets the status of the iteration once the calculation is finished. + /// + public ICalculationStatus IterationResult + { + get + { + return (_iterator != null) ? _iterator.Status : DefaultStatus; + } + } + + /// + /// Stops the solve process. + /// + /// + /// Note that it may take an indetermined amount of time for the solver to actually + /// stop the process. + /// + public void StopSolve() + { + _hasBeenStopped = true; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b. + /// The result vector, x. + public Vector Solve(Matrix matrix, Vector vector) + { + if (vector == null) + { + throw new ArgumentNullException(); + } + + Vector result = new DenseVector(matrix.RowCount); + Solve(matrix, vector, result); + return result; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + public void Solve(Matrix matrix, Vector input, Vector result) + { + // If we were stopped before, we are no longer + // We're doing this at the start of the method to ensure + // that we can use these fields immediately. + _hasBeenStopped = false; + + // Error checks + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Initialize the solver fields + + // Set the convergence monitor + if (_iterator == null) + { + _iterator = Iterator.CreateDefault(); + } + + if (_preconditioner == null) + { + _preconditioner = new UnitPreconditioner(); + } + + _preconditioner.Initialize(matrix); + + // x_0 is initial guess + // Take x_0 = 0 + Vector xtemp = new DenseVector(input.Count); + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + Vector residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + double beta = 0; + double sigma; + + // Define the temporary vectors + // rDash_0 = r_0 + Vector rdash = new DenseVector(residuals); + + // t_-1 = 0 + Vector t = new DenseVector(residuals.Count); + Vector t0 = new DenseVector(residuals.Count); + + // w_-1 = 0 + Vector w = new DenseVector(residuals.Count); + + // Define the remaining temporary vectors + Vector c = new DenseVector(residuals.Count); + Vector p = new DenseVector(residuals.Count); + Vector s = new DenseVector(residuals.Count); + Vector u = new DenseVector(residuals.Count); + Vector y = new DenseVector(residuals.Count); + Vector z = new DenseVector(residuals.Count); + + Vector temp = new DenseVector(residuals.Count); + Vector mult = new DenseVector(residuals.Count); + + // for (k = 0, 1, .... ) + var iterationNumber = 0; + while (ShouldContinue(iterationNumber, xtemp, input, residuals)) + { + // p_k = r_k + beta_(k-1) * (p_(k-1) - u_(k-1)) + p.Subtract(u, temp); + + temp.Multiply(beta, mult); + residuals.Add(mult, p); + + // Solve M b_k = p_k + _preconditioner.Approximate(p, temp); + + // s_k = A b_k + matrix.Multiply(temp, s); + + // alpha_k = (r*_0 * r_k) / (r*_0 * s_k) + var alpha = rdash.DotProduct(residuals) / rdash.DotProduct(s); + + // y_k = t_(k-1) - r_k - alpha_k * w_(k-1) + alpha_k s_k + s.Subtract(w, temp); + t.Subtract(residuals, y); + + temp.Multiply(alpha, mult); + y.Add(mult); + + // Store the old value of t in t0 + t.CopyTo(t0); + + // t_k = r_k - alpha_k s_k + s.Multiply(-alpha, mult); + residuals.Add(mult, t); + + // Solve M d_k = t_k + _preconditioner.Approximate(t, temp); + + // c_k = A d_k + matrix.Multiply(temp, c); + var cdot = c.DotProduct(c); + + // cDot can only be zero if c is a zero vector + // We'll set cDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // c.DotProduct(t) will be zero and so will c.DotProduct(y) + if (cdot.AlmostEqual(0, 1)) + { + cdot = 1.0; + } + + // Even if we don't want to do any BiCGStab steps we'll still have + // to do at least one at the start to initialize the + // system, but we'll only have to take special measures + // if we don't do any so ... + var ctdot = c.DotProduct(t); + double eta; + if (((_numberOfBiCgStabSteps == 0) && (iterationNumber == 0)) || ShouldRunBiCgStabSteps(iterationNumber)) + { + // sigma_k = (c_k * t_k) / (c_k * c_k) + sigma = ctdot / cdot; + + // eta_k = 0 + eta = 0; + } + else + { + var ydot = y.DotProduct(y); + + // yDot can only be zero if y is a zero vector + // We'll set yDot to 1 if it is zero to prevent NaN's + // Note that the calculation should continue fine because + // y.DotProduct(t) will be zero and so will c.DotProduct(y) + if (ydot.AlmostEqual(0, 1)) + { + ydot = 1.0; + } + + var ytdot = y.DotProduct(t); + var cydot = c.DotProduct(y); + + var denom = (cdot * ydot) - (cydot * cydot); + + // sigma_k = ((y_k * y_k)(c_k * t_k) - (y_k * t_k)(c_k * y_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + sigma = ((ydot * ctdot) - (ytdot * cydot)) / denom; + + // eta_k = ((c_k * c_k)(y_k * t_k) - (y_k * c_k)(c_k * t_k)) / ((c_k * c_k)(y_k * y_k) - (y_k * c_k)(c_k * y_k)) + eta = ((cdot * ytdot) - (cydot * ctdot)) / denom; + } + + // u_k = sigma_k s_k + eta_k (t_(k-1) - r_k + beta_(k-1) u_(k-1)) + u.Multiply(beta, mult); + t0.Add(mult, temp); + + temp.Subtract(residuals); + temp.Multiply(eta); + + s.Multiply(sigma, mult); + temp.Add(mult, u); + + // z_k = sigma_k r_k +_ eta_k z_(k-1) - alpha_k u_k + z.Multiply(eta); + + u.Multiply(-alpha, mult); + z.Add(mult); + + residuals.Multiply(sigma, mult); + z.Add(mult); + + // x_(k+1) = x_k + alpha_k p_k + z_k + p.Multiply(alpha, mult); + xtemp.Add(mult); + + xtemp.Add(z); + + // r_(k+1) = t_k - eta_k y_k - sigma_k c_k + // Copy the old residuals to a temp vector because we'll + // need those in the next step + residuals.CopyTo(t0); + + y.Multiply(-eta, mult); + t.Add(mult, residuals); + + c.Multiply(-sigma, mult); + residuals.Add(mult); + + // beta_k = alpha_k / sigma_k * (r*_0 * r_(k+1)) / (r*_0 * r_k) + // But first we check if there is a possible NaN. If so just reset beta to zero. + beta = (!sigma.AlmostEqual(0, 1)) ? alpha / sigma * rdash.DotProduct(residuals) / rdash.DotProduct(t0) : 0; + + // w_k = c_k + beta_k s_k + s.Multiply(beta, mult); + c.Add(mult, w); + + // Get the real value + _preconditioner.Approximate(xtemp, result); + + // Now check for convergence + if (!ShouldContinue(iterationNumber, result, input, residuals)) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, result, input); + } + + // Next iteration. + iterationNumber++; + } + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + private static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1); + + // residual + b + residual.Add(b); + } + + /// + /// Determine if calculation should continue + /// + /// Number of iterations passed + /// Result . + /// Source . + /// Residual . + /// true if continue, otherwise false + private bool ShouldContinue(int iterationNumber, Vector result, Vector source, Vector residuals) + { + if (_hasBeenStopped) + { + _iterator.IterationCancelled(); + return true; + } + + _iterator.DetermineStatus(iterationNumber, result, source, residuals); + var status = _iterator.Status; + + // We stop if either: + // - the user has stopped the calculation + // - the calculation needs to be stopped from a numerical point of view (divergence, convergence etc.) + return (!status.TerminatesCalculation) && (!_hasBeenStopped); + } + + /// + /// Decide if to do steps with BiCgStab + /// + /// Number of iteration + /// true if yes, otherwise false + private bool ShouldRunBiCgStabSteps(int iterationNumber) + { + // Run the first steps as BiCGStab + // The number of steps past a whole iteration set + var difference = iterationNumber % (_numberOfBiCgStabSteps + _numberOfGpbiCgSteps); + + // Do steps with BiCGStab if: + // - The difference is zero or more (i.e. we have done zero or more complete cycles) + // - The difference is less than the number of BiCGStab steps that should be taken + return (difference >= 0) && (difference < _numberOfBiCgStabSteps); + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X. + public Matrix Solve(Matrix matrix, Matrix input) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var result = matrix.CreateMatrix(input.RowCount, input.ColumnCount); + Solve(matrix, input, result); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X + public void Solve(Matrix matrix, Matrix input, Matrix result) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (matrix.RowCount != input.RowCount || input.RowCount != result.RowCount || input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + for (var column = 0; column < input.ColumnCount; column++) + { + var solution = Solve(matrix, input.Column(column)); + foreach (var element in solution.GetIndexedEnumerator()) + { + result.At(element.Key, column, element.Value); + } + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs new file mode 100644 index 00000000..cd9f5d29 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs @@ -0,0 +1,788 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Iterative +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Distributions; + using Factorization; + using Preconditioners; + using Properties; + using Status; + + /// + /// A Multiple-Lanczos Bi-Conjugate Gradient stabilized iterative matrix solver. + /// + /// + /// + /// The Multiple-Lanczos Bi-Conjugate Gradient stabilized (ML(k)-BiCGStab) solver is an 'improvement' + /// of the standard BiCgStab solver. + /// + /// + /// The algorithm was taken from:
+ /// ML(k)BiCGSTAB: A BiCGSTAB variant based on multiple Lanczos starting vectors + ///
+ /// Man-chung Yeung and Tony F. Chan + ///
+ /// SIAM Journal of Scientific Computing + ///
+ /// Volume 21, Number 4, pp. 1263 - 1290 + ///
+ /// + /// The example code below provides an indication of the possible use of the + /// solver. + /// + ///
+ public sealed class MlkBiCgStab : IIterativeSolver + { + /// + /// The default number of starting vectors. + /// + private const int DefaultNumberOfStartingVectors = 50; + + /// + /// The status used if there is no status, i.e. the solver hasn't run yet and there is no + /// iterator. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The preconditioner that will be used. Can be set to , in which case the default + /// pre-conditioner will be used. + /// + private IPreConditioner _preconditioner; + + /// + /// The iterative process controller. + /// + private IIterator _iterator; + + /// + /// The collection of starting vectors which are used as the basis for the Krylov sub-space. + /// + private IList _startingVectors; + + /// + /// The number of starting vectors used by the algorithm + /// + private int _numberOfStartingVectors = DefaultNumberOfStartingVectors; + + /// + /// Indicates if the user has stopped the solver. + /// + private bool _hasBeenStopped; + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings and a default preconditioner. + /// + public MlkBiCgStab() : this(null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// When using this constructor the solver will use a default preconditioner. + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to monitor the iterative process. + public MlkBiCgStab(IIterator iterator) : this(null, iterator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings. + /// + /// The that will be used to precondition the matrix equation. + public MlkBiCgStab(IPreConditioner preconditioner) : this(preconditioner, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to precondition the matrix equation. + /// The that will be used to monitor the iterative process. + public MlkBiCgStab(IPreConditioner preconditioner, IIterator iterator) + { + _iterator = iterator; + _preconditioner = preconditioner; + } + + /// + /// Gets or sets the number of starting vectors. + /// + /// + /// Must be larger than 1 and smaller than the number of variables in the matrix that + /// for which this solver will be used. + /// + public int NumberOfStartingVectors + { + [DebuggerStepThrough] + get + { + return _numberOfStartingVectors; + } + + [DebuggerStepThrough] + set + { + if (value <= 1) + { + throw new ArgumentOutOfRangeException("value"); + } + + _numberOfStartingVectors = value; + } + } + + /// + /// Resets the number of starting vectors to the default value. + /// + public void ResetNumberOfStartingVectors() + { + _numberOfStartingVectors = DefaultNumberOfStartingVectors; + } + + /// + /// Sets the that will be used to precondition the iterative process. + /// + /// The preconditioner. + public void SetPreconditioner(IPreConditioner preconditioner) + { + _preconditioner = preconditioner; + } + + /// + /// Sets the that will be used to track the iterative process. + /// + /// The iterator. + public void SetIterator(IIterator iterator) + { + _iterator = iterator; + } + + /// + /// Gets or sets a series of orthonormal vectors which will be used as basis for the + /// Krylov sub-space. + /// + public IList StartingVectors + { + [DebuggerStepThrough] + get + { + return _startingVectors; + } + + [DebuggerStepThrough] + set + { + if ((value == null) || (value.Count == 0)) + { + _startingVectors = null; + } + else + { + _startingVectors = value; + } + } + } + + /// + /// Gets the status of the iteration once the calculation is finished. + /// + public ICalculationStatus IterationResult + { + [DebuggerStepThrough] + get + { + return (_iterator != null) ? _iterator.Status : DefaultStatus; + } + } + + /// + /// Stops the solve process. + /// + /// + /// Note that it may take an indetermined amount of time for the solver to actually stop the process. + /// + public void StopSolve() + { + _hasBeenStopped = true; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b. + /// The result vector, x. + public Vector Solve(Matrix matrix, Vector vector) + { + if (vector == null) + { + throw new ArgumentNullException(); + } + + Vector result = new DenseVector(matrix.RowCount); + Solve(matrix, vector, result); + return result; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + public void Solve(Matrix matrix, Vector input, Vector result) + { + // If we were stopped before, we are no longer + // We're doing this at the start of the method to ensure + // that we can use these fields immediately. + _hasBeenStopped = false; + + // Error checks + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Initialize the solver fields + // Set the convergence monitor + if (_iterator == null) + { + _iterator = Iterator.CreateDefault(); + } + + if (_preconditioner == null) + { + _preconditioner = new UnitPreconditioner(); + } + + _preconditioner.Initialize(matrix); + + // Choose an initial guess x_0 + // Take x_0 = 0 + Vector xtemp = new DenseVector(input.Count); + + // Choose k vectors q_1, q_2, ..., q_k + // Build a new set if: + // a) the stored set doesn't exist (i.e. == null) + // b) Is of an incorrect length (i.e. too long) + // c) The vectors are of an incorrect length (i.e. too long or too short) + var useOld = false; + if (_startingVectors != null) + { + // We don't accept collections with zero starting vectors so ... + if (_startingVectors.Count <= NumberOfStartingVectorsToCreate(_numberOfStartingVectors, input.Count)) + { + // Only check the first vector for sizing. If that matches we assume the + // other vectors match too. If they don't the process will crash + if (_startingVectors[0].Count == input.Count) + { + useOld = true; + } + } + } + + _startingVectors = useOld ? _startingVectors : CreateStartingVectors(_numberOfStartingVectors, input.Count); + + // Store the number of starting vectors. Not really necessary but easier to type :) + var k = _startingVectors.Count; + + // r_0 = b - Ax_0 + // This is basically a SAXPY so it could be made a lot faster + Vector residuals = new DenseVector(matrix.RowCount); + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Define the temporary scalars + var c = new double[k]; + + // Define the temporary vectors + Vector gtemp = new DenseVector(residuals.Count); + + Vector u = new DenseVector(residuals.Count); + Vector utemp = new DenseVector(residuals.Count); + Vector temp = new DenseVector(residuals.Count); + Vector temp1 = new DenseVector(residuals.Count); + + Vector zd = new DenseVector(residuals.Count); + Vector zg = new DenseVector(residuals.Count); + Vector zw = new DenseVector(residuals.Count); + + Vector mult = new DenseVector(residuals.Count); + + var d = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // g_0 = r_0 + var g = CreateVectorArray(_startingVectors.Count, residuals.Count); + residuals.CopyTo(g[k - 1]); + + var w = CreateVectorArray(_startingVectors.Count, residuals.Count); + + // FOR (j = 0, 1, 2 ....) + var iterationNumber = 0; + while (ShouldContinue(iterationNumber, xtemp, input, residuals)) + { + // SOLVE M g~_((j-1)k+k) = g_((j-1)k+k) + _preconditioner.Approximate(g[k - 1], gtemp); + + // w_((j-1)k+k) = A g~_((j-1)k+k) + matrix.Multiply(gtemp, w[k - 1]); + + // c_((j-1)k+k) = q^T_1 w_((j-1)k+k) + c[k - 1] = _startingVectors[0].DotProduct(w[k - 1]); + if (c[k - 1].AlmostEqual(0, 1)) + { + throw new Exception("Iterative solver experience a numerical break down"); + } + + // alpha_(jk+1) = q^T_1 r_((j-1)k+k) / c_((j-1)k+k) + var alpha = _startingVectors[0].DotProduct(residuals) / c[k - 1]; + + // u_(jk+1) = r_((j-1)k+k) - alpha_(jk+1) w_((j-1)k+k) + w[k - 1].Multiply(-alpha, mult); + residuals.Add(mult, u); + + // SOLVE M u~_(jk+1) = u_(jk+1) + _preconditioner.Approximate(u, temp1); + temp1.CopyTo(utemp); + + // rho_(j+1) = -u^t_(jk+1) A u~_(jk+1) / ||A u~_(jk+1)||^2 + matrix.Multiply(temp1, temp); + var rho = temp.DotProduct(temp); + + // If rho is zero then temp is a zero vector and we're probably + // about to have zero residuals (i.e. an exact solution). + // So set rho to 1.0 because in the next step it will turn to zero. + if (rho.AlmostEqual(0, 1)) + { + rho = 1.0; + } + + rho = -u.DotProduct(temp) / rho; + + // x_(jk+1) = x_((j-1)k_k) - rho_(j+1) u~_(jk+1) + alpha_(jk+1) g~_((j-1)k+k) + utemp.Multiply(-rho, mult); + xtemp.Add(mult); + + gtemp.Multiply(alpha); + xtemp.Add(gtemp); + + // r_(jk+1) = rho_(j+1) A u~_(jk+1) + u_(jk+1) + u.CopyTo(residuals); + + // Reuse temp + temp.Multiply(rho); + residuals.Add(temp); + + // Check convergence and stop if we are converged. + if (!ShouldContinue(iterationNumber, xtemp, input, residuals)) + { + // Calculate the true residual + CalculateTrueResidual(matrix, residuals, xtemp, input); + + // Now recheck the convergence + if (!ShouldContinue(iterationNumber, xtemp, input, residuals)) + { + // We're all good now. + // Exit from the while loop. + break; + } + } + + // FOR (i = 1,2, ...., k) + for (var i = 0; i < k; i++) + { + // z_d = u_(jk+1) + u.CopyTo(zd); + + // z_g = r_(jk+i) + residuals.CopyTo(zg); + + // z_w = 0 + zw.Clear(); + + // FOR (s = i, ...., k-1) AND j >= 1 + double beta; + if (iterationNumber >= 1) + { + for (var s = i; s < k - 1; s++) + { + // beta^(jk+i)_((j-1)k+s) = -q^t_(s+1) z_d / c_((j-1)k+s) + beta = -_startingVectors[s + 1].DotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_((j-1)k+s) d_((j-1)k+s) + d[s].Multiply(beta, mult); + zd.Add(mult); + + // z_g = z_g + beta^(jk+i)_((j-1)k+s) g_((j-1)k+s) + g[s].Multiply(beta, mult); + zg.Add(mult); + + // z_w = z_w + beta^(jk+i)_((j-1)k+s) w_((j-1)k+s) + w[s].Multiply(beta, mult); + zw.Add(mult); + } + } + + beta = rho * c[k - 1]; + if (beta.AlmostEqual(0, 1)) + { + throw new Exception("Iterative solver experience a numerical break down"); + } + + // beta^(jk+i)_((j-1)k+k) = -(q^T_1 (r_(jk+1) + rho_(j+1) z_w)) / (rho_(j+1) c_((j-1)k+k)) + zw.Multiply(rho, mult); + residuals.Add(mult, temp); + beta = -_startingVectors[0].DotProduct(temp) / beta; + + // z_g = z_g + beta^(jk+i)_((j-1)k+k) g_((j-1)k+k) + g[k - 1].Multiply(beta, mult); + zg.Add(mult); + + // z_w = rho_(j+1) (z_w + beta^(jk+i)_((j-1)k+k) w_((j-1)k+k)) + w[k - 1].Multiply(beta, mult); + zw.Add(mult); + zw.Multiply(rho); + + // z_d = r_(jk+i) + z_w + residuals.Add(zw, zd); + + // FOR (s = 1, ... i - 1) + for (var s = 0; s < i - 1; s++) + { + // beta^(jk+i)_(jk+s) = -q^T_s+1 z_d / c_(jk+s) + beta = -_startingVectors[s + 1].DotProduct(zd) / c[s]; + + // z_d = z_d + beta^(jk+i)_(jk+s) * d_(jk+s) + d[s].Multiply(beta, mult); + zd.Add(mult); + + // z_g = z_g + beta^(jk+i)_(jk+s) * g_(jk+s) + g[s].Multiply(beta, mult); + zg.Add(mult); + } + + // d_(jk+i) = z_d - u_(jk+i) + zd.Subtract(u, d[i]); + + // g_(jk+i) = z_g + z_w + zg.Add(zw, g[i]); + + // IF (i < k - 1) + if (i < k - 1) + { + // c_(jk+1) = q^T_i+1 d_(jk+i) + c[i] = _startingVectors[i + 1].DotProduct(d[i]); + if (c[i].AlmostEqual(0, 1)) + { + throw new Exception("Iterative solver experience a numerical break down"); + } + + // alpha_(jk+i+1) = q^T_(i+1) u_(jk+i) / c_(jk+i) + alpha = _startingVectors[i + 1].DotProduct(u) / c[i]; + + // u_(jk+i+1) = u_(jk+i) - alpha_(jk+i+1) d_(jk+i) + d[i].Multiply(-alpha, mult); + u.Add(mult); + + // SOLVE M g~_(jk+i) = g_(jk+i) + _preconditioner.Approximate(g[i], gtemp); + + // x_(jk+i+1) = x_(jk+i) + rho_(j+1) alpha_(jk+i+1) g~_(jk+i) + gtemp.Multiply(rho * alpha, mult); + xtemp.Add(mult); + + // w_(jk+i) = A g~_(jk+i) + matrix.Multiply(gtemp, w[i]); + + // r_(jk+i+1) = r_(jk+i) - rho_(j+1) alpha_(jk+i+1) w_(jk+i) + w[i].Multiply(-rho * alpha, mult); + residuals.Add(mult); + + // We can check the residuals here if they're close + if (!ShouldContinue(iterationNumber, xtemp, input, residuals)) + { + // Recalculate the residuals and go round again. This is done to ensure that + // we have the proper residuals. + CalculateTrueResidual(matrix, residuals, xtemp, input); + } + } + } // END ITERATION OVER i + + iterationNumber++; + } + + // copy the temporary result to the real result vector + xtemp.CopyTo(result); + } + + /// + /// Gets the number of starting vectors to create + /// + /// Maximum number + /// Number of variables + /// Number of starting vectors to create + private static int NumberOfStartingVectorsToCreate(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + return Math.Min(maximumNumberOfStartingVectors, (numberOfVariables - 1)); + } + + /// + /// Returns an array of starting vectors. + /// + /// The maximum number of starting vectors that should be created. + /// The number of variables. + /// + /// An array with starting vectors. The array will never be larger than the + /// but it may be smaller if + /// the is smaller than + /// the . + /// + private static IList CreateStartingVectors(int maximumNumberOfStartingVectors, int numberOfVariables) + { + // Create no more starting vectors than the size of the problem - 1 + // Get random values and then orthogonalize them with + // modified Gramm - Schmidt + var count = NumberOfStartingVectorsToCreate(maximumNumberOfStartingVectors, numberOfVariables); + + // Get a random set of samples based on the standard normal distribution with + // mean = 0 and sd = 1 + var distribution = new Normal(); + + Matrix matrix = new DenseMatrix(numberOfVariables, count); + for (var i = 0; i < matrix.ColumnCount; i++) + { + var samples = distribution.Samples().Take(matrix.RowCount).ToArray(); + + // Set the column + matrix.SetColumn(i, samples); + } + + // Compute the orthogonalization. + var gs = new GramSchmidt(matrix); + var orthogonalMatrix = gs.Q; + + // Now transfer this to vectors + var result = new List(); + for (var i = 0; i < orthogonalMatrix.ColumnCount; i++) + { + result.Add(orthogonalMatrix.Column(i)); + + // Normalize the result vector + result[i].Multiply(1 / result[i].Norm(2)); + } + + return result; + } + + /// + /// Create random vecrors array + /// + /// Number of vectors + /// Size of each vector + /// Array of random vectors + private static Vector[] CreateVectorArray(int arraySize, int vectorSize) + { + var result = new Vector[arraySize]; + for (var i = 0; i < result.Length; i++) + { + result[i] = new DenseVector(vectorSize); + } + + return result; + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Source A. + /// Residual data. + /// x data. + /// b data. + private static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1); + + // residual + b + residual.Add(b); + } + + /// + /// Determine if calculation should continue + /// + /// Number of iterations passed + /// Result . + /// Source . + /// Residual . + /// true if continue, otherwise false + private bool ShouldContinue(int iterationNumber, Vector result, Vector source, Vector residuals) + { + if (_hasBeenStopped) + { + _iterator.IterationCancelled(); + return true; + } + + _iterator.DetermineStatus(iterationNumber, result, source, residuals); + var status = _iterator.Status; + + // We stop if either: + // - the user has stopped the calculation + // - the calculation needs to be stopped from a numerical point of view (divergence, convergence etc.) + return (!status.TerminatesCalculation) && (!_hasBeenStopped); + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X. + public Matrix Solve(Matrix matrix, Matrix input) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var result = matrix.CreateMatrix(input.RowCount, input.ColumnCount); + Solve(matrix, input, result); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X + public void Solve(Matrix matrix, Matrix input, Matrix result) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (matrix.RowCount != input.RowCount || input.RowCount != result.RowCount || input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + for (var column = 0; column < input.ColumnCount; column++) + { + var solution = Solve(matrix, input.Column(column)); + foreach (var element in solution.GetIndexedEnumerator()) + { + result.At(element.Key, column, element.Value); + } + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/TFQMR.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/TFQMR.cs new file mode 100644 index 00000000..b06df5a9 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/TFQMR.cs @@ -0,0 +1,532 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Iterative +{ + using System; + using Preconditioners; + using Properties; + using Status; + + /// + /// A Transpose Free Quasi-Minimal Residual (TFQMR) iterative matrix solver. + /// + /// + /// + /// The TFQMR algorithm was taken from:
+ /// Iterative methods for sparse linear systems. + ///
+ /// Yousef Saad + ///
+ /// Algorithm is described in Chapter 7, section 7.4.3, page 219 + ///
+ /// + /// The example code below provides an indication of the possible use of the + /// solver. + /// + ///
+ public sealed class TFQMR : IIterativeSolver + { + /// + /// The status used if there is no status, i.e. the solver hasn't run yet and there is no + /// iterator. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The preconditioner that will be used. Can be set to , in which case the default + /// pre-conditioner will be used. + /// + private IPreConditioner _preconditioner; + + /// + /// The iterative process controller. + /// + private IIterator _iterator; + + /// + /// Indicates if the user has stopped the solver. + /// + private bool _hasBeenStopped; + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings and a default preconditioner. + /// + public TFQMR() : this(null, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// When using this constructor the solver will use a default preconditioner. + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to monitor the iterative process. + public TFQMR(IIterator iterator) : this(null, iterator) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// When using this constructor the solver will use the with + /// the standard settings. + /// + /// The that will be used to precondition the matrix equation. + public TFQMR(IPreConditioner preconditioner) : this(preconditioner, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// The main advantages of using a user defined are: + /// + /// It is possible to set the desired convergence limits. + /// + /// It is possible to check the reason for which the solver finished + /// the iterative procedure by calling the property. + /// + /// + /// + /// + /// The that will be used to precondition the matrix equation. + /// The that will be used to monitor the iterative process. + public TFQMR(IPreConditioner preconditioner, IIterator iterator) + { + _iterator = iterator; + _preconditioner = preconditioner; + } + + /// + /// Sets the that will be used to precondition the iterative process. + /// + /// The preconditioner. + public void SetPreconditioner(IPreConditioner preconditioner) + { + _preconditioner = preconditioner; + } + + /// + /// Sets the that will be used to track the iterative process. + /// + /// The iterator. + public void SetIterator(IIterator iterator) + { + _iterator = iterator; + } + + /// + /// Gets the status of the iteration once the calculation is finished. + /// + public ICalculationStatus IterationResult + { + get + { + return (_iterator != null) ? _iterator.Status : DefaultStatus; + } + } + + /// + /// Stops the solve process. + /// + /// + /// Note that it may take an indetermined amount of time for the solver to actually stop the process. + /// + public void StopSolve() + { + _hasBeenStopped = true; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b. + /// The result vector, x. + public Vector Solve(Matrix matrix, Vector vector) + { + if (vector == null) + { + throw new ArgumentNullException(); + } + + Vector result = new DenseVector(matrix.RowCount); + Solve(matrix, vector, result); + return result; + } + + /// + /// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the + /// solution vector and x is the unknown vector. + /// + /// The coefficient matrix, A. + /// The solution vector, b + /// The result vector, x + public void Solve(Matrix matrix, Vector input, Vector result) + { + // If we were stopped before, we are no longer + // We're doing this at the start of the method to ensure + // that we can use these fields immediately. + _hasBeenStopped = false; + + // Error checks + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (result.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Initialize the solver fields + // Set the convergence monitor + if (_iterator == null) + { + _iterator = Iterator.CreateDefault(); + } + + if (_preconditioner == null) + { + _preconditioner = new UnitPreconditioner(); + } + + _preconditioner.Initialize(matrix); + + var d = new DenseVector(input.Count); + var r = new DenseVector(input); + + var uodd = new DenseVector(input.Count); + var ueven = new DenseVector(input.Count); + + var v = new DenseVector(input.Count); + var pseudoResiduals = new DenseVector(input); + + var x = new DenseVector(input.Count); + var yodd = new DenseVector(input.Count); + var yeven = new DenseVector(input); + + // Temp vectors + var temp = new DenseVector(input.Count); + var mult = new DenseVector(input.Count); + + // Initialize + var startNorm = input.Norm(2); + + // Define the scalars + double alpha = 0; + double eta = 0; + double theta = 0; + + var tau = startNorm; + var rho = tau * tau; + + // Calculate the initial values for v + // M temp = yEven + _preconditioner.Approximate(yeven, temp); + + // v = A temp + matrix.Multiply(temp, v); + + // Set uOdd + v.CopyTo(ueven); + + // Start the iteration + var iterationNumber = 0; + while (ShouldContinue(iterationNumber, result, input, pseudoResiduals)) + { + // First part of the step, the even bit + if (IsEven(iterationNumber)) + { + // sigma = (v, r) + var sigma = v.DotProduct(r); + if (sigma.AlmostEqual(0, 1)) + { + // FAIL HERE + _iterator.IterationCancelled(); + break; + } + + // alpha = rho / sigma + alpha = rho / sigma; + + // yOdd = yEven - alpha * v + v.Multiply(-alpha, mult); + yeven.Add(mult, yodd); + + // Solve M temp = yOdd + _preconditioner.Approximate(yodd, temp); + + // uOdd = A temp + matrix.Multiply(temp, uodd); + } + + // The intermediate step which is equal for both even and + // odd iteration steps. + // Select the correct vector + var uinternal = IsEven(iterationNumber) ? ueven : uodd; + var yinternal = IsEven(iterationNumber) ? yeven : yodd; + + // pseudoResiduals = pseudoResiduals - alpha * uOdd + uinternal.Multiply(-alpha, mult); + pseudoResiduals.Add(mult); + + // d = yOdd + theta * theta * eta / alpha * d + d.Multiply(theta * theta * eta / alpha, temp); + yinternal.Add(temp, d); + + // theta = ||pseudoResiduals||_2 / tau + theta = pseudoResiduals.Norm(2) / tau; + var c = 1 / Math.Sqrt(1 + (theta * theta)); + + // tau = tau * theta * c + tau *= theta * c; + + // eta = c^2 * alpha + eta = c * c * alpha; + + // x = x + eta * d + d.Multiply(eta, mult); + x.Add(mult); + + // Check convergence and see if we can bail + if (!ShouldContinue(iterationNumber, result, input, pseudoResiduals)) + { + // Calculate the real values + _preconditioner.Approximate(x, result); + + // Calculate the true residual. Use the temp vector for that + // so that we don't pollute the pseudoResidual vector for no + // good reason. + CalculateTrueResidual(matrix, temp, result, input); + + // Now recheck the convergence + if (!ShouldContinue(iterationNumber, result, input, temp)) + { + // We're all good now. + return; + } + } + + // The odd step + if (!IsEven(iterationNumber)) + { + if (rho.AlmostEqual(0, 1)) + { + // FAIL HERE + _iterator.IterationCancelled(); + break; + } + + var rhoNew = pseudoResiduals.DotProduct(r); + var beta = rhoNew / rho; + + // Update rho for the next loop + rho = rhoNew; + + // yOdd = pseudoResiduals + beta * yOdd + yodd.Multiply(beta, mult); + pseudoResiduals.Add(mult, yeven); + + // Solve M temp = yOdd + _preconditioner.Approximate(yeven, temp); + + // uOdd = A temp + matrix.Multiply(temp, ueven); + + // v = uEven + beta * (uOdd + beta * v) + v.Multiply(beta, mult); + uodd.Add(mult, temp); + + temp.Multiply(beta, mult); + ueven.Add(mult, v); + } + + // Calculate the real values + _preconditioner.Approximate(x, result); + + iterationNumber++; + } + } + + /// + /// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax + /// + /// Instance of the A. + /// Residual values in . + /// Instance of the x. + /// Instance of the b. + private static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b) + { + // -Ax = residual + matrix.Multiply(x, residual); + residual.Multiply(-1); + + // residual + b + residual.Add(b); + } + + /// + /// Determine if calculation should continue + /// + /// Number of iterations passed + /// Result . + /// Source . + /// Residual . + /// true if continue, otherwise false + private bool ShouldContinue(int iterationNumber, Vector result, Vector source, Vector residuals) + { + if (_hasBeenStopped) + { + _iterator.IterationCancelled(); + return true; + } + + _iterator.DetermineStatus(iterationNumber, result, source, residuals); + var status = _iterator.Status; + + // We stop if either: + // - the user has stopped the calculation + // - the calculation needs to be stopped from a numerical point of view (divergence, convergence etc.) + return (!status.TerminatesCalculation) && (!_hasBeenStopped); + } + + /// + /// Is even? + /// + /// Number to check + /// true if even, otherwise false + private static bool IsEven(int number) + { + return number % 2 == 0; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X. + public Matrix Solve(Matrix matrix, Matrix input) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var result = matrix.CreateMatrix(input.RowCount, input.ColumnCount); + Solve(matrix, input, result); + return result; + } + + /// + /// Solves the matrix equation AX = B, where A is the coefficient matrix, B is the + /// solution matrix and X is the unknown matrix. + /// + /// The coefficient matrix, A. + /// The solution matrix, B. + /// The result matrix, X + public void Solve(Matrix matrix, Matrix input, Matrix result) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (matrix.RowCount != input.RowCount || input.RowCount != result.RowCount || input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + for (var column = 0; column < input.ColumnCount; column++) + { + var solution = Solve(matrix, input.Column(column)); + foreach (var element in solution.GetIndexedEnumerator()) + { + result.At(element.Key, column, element.Value); + } + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Iterator.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Iterator.cs new file mode 100644 index 00000000..1caad4ef --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Iterator.cs @@ -0,0 +1,325 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Properties; + using Status; + using StopCriterium; + + /// + /// An iterator that is used to check if an iterative calculation should continue or stop. + /// + public sealed class Iterator : IIterator + { + /// + /// The default status for the iterator. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// Creates a default iterator with all the objects. + /// + /// A new object. + public static IIterator CreateDefault() + { + var iterator = new Iterator(); + iterator.Add(new FailureStopCriterium()); + iterator.Add(new DivergenceStopCriterium()); + iterator.Add(new IterationCountStopCriterium()); + iterator.Add(new ResidualStopCriterium()); + + return iterator; + } + + /// + /// The collection that holds all the stop criteria and the flag indicating if they should be added + /// to the child iterators. + /// + private readonly Dictionary _stopCriterias = new Dictionary(); + + /// + /// The status of the iterator. + /// + private ICalculationStatus _status = DefaultStatus; + + /// + /// Indicates if the iteration was cancelled. + /// + private bool _wasIterationCancelled; + + /// + /// Initializes a new instance of the class. + /// + public Iterator() : this(null) + { + } + + /// + /// Initializes a new instance of the class with the specified stop criteria. + /// + /// + /// The specified stop criteria. Only one stop criterium of each type can be passed in. None + /// of the stop criteria will be passed on to child iterators. + /// + /// Thrown if contains multiple stop criteria of the same type. + public Iterator(IEnumerable stopCriteria) + { + // Add the stop criteria + if (stopCriteria == null) + { + return; + } + + foreach (var stopCriterium in stopCriteria.Where(stopCriterium => stopCriterium != null)) + { + Add(stopCriterium); + } + } + + /// + /// Adds an to the internal collection of stop-criteria. Only a + /// single stop criterium of each type can be stored. + /// + /// The stop criterium to add. + /// Thrown if is . + /// + /// Thrown if is of the same type as an already + /// stored criterium. + /// + public void Add(IIterationStopCriterium stopCriterium) + { + if (stopCriterium == null) + { + throw new ArgumentNullException("stopCriterium"); + } + + if (_stopCriterias.ContainsKey(stopCriterium.GetType())) + { + throw new ArgumentException(Resources.StopCriteriumDuplicate); + } + + // Store the stop criterium. + _stopCriterias.Add(stopCriterium.GetType(), stopCriterium); + } + + /// + /// Removes the from the internal collection. + /// + /// The stop criterium that must be removed. + public void Remove(IIterationStopCriterium stopCriterium) + { + if (stopCriterium == null) + { + throw new ArgumentNullException("stopCriterium"); + } + + if (!_stopCriterias.ContainsKey(stopCriterium.GetType())) + { + return; + } + + // Remove from the collection + _stopCriterias.Remove(stopCriterium.GetType()); + } + + /// + /// Indicates if the specific stop criterium is stored by the . + /// + /// The stop criterium. + /// true if the contains the stop criterium; otherwise false. + public bool Contains(IIterationStopCriterium stopCriterium) + { + return stopCriterium != null && _stopCriterias.ContainsKey(stopCriterium.GetType()); + } + + /// + /// Gets the number of stored stop criteria. + /// + /// Used for testing only. + internal int NumberOfCriteria + { + get + { + return _stopCriterias.Count; + } + } + + /// + /// Gets an IEnumerator that enumerates over all the stored stop criteria. + /// + /// Used for testing only. + internal IEnumerator StoredStopCriteria + { + get + { + return _stopCriterias.Select(criterium => criterium.Value).GetEnumerator(); + } + } + + /// + /// Indicates to the iterator that the iterative process has been cancelled. + /// + /// + /// Does not reset the stop-criteria. + /// + public void IterationCancelled() + { + _wasIterationCancelled = true; + _status = new CalculationCancelled(); + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current IIterator. Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual iterators may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (_stopCriterias.Count == 0) + { + throw new ArgumentException(Resources.StopCriteriumMissing); + } + + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException("iterationNumber"); + } + + if (solutionVector == null) + { + throw new ArgumentNullException("solutionVector"); + } + + if (sourceVector == null) + { + throw new ArgumentNullException("sourceVector"); + } + + if (residualVector == null) + { + throw new ArgumentNullException("residualVector"); + } + + // While we're cancelled we don't call on the stop-criteria. + if (_wasIterationCancelled) + { + return; + } + + foreach (var stopCriterium in _stopCriterias.Select(pair => pair.Value)) + { + stopCriterium.DetermineStatus(iterationNumber, solutionVector, sourceVector, residualVector); + var status = stopCriterium.Status; + + // Check if the status is: + // - Running --> keep going + // - Indetermined --> keep going + // Anything else: + // Stop looping and set that status + if ((status is CalculationRunning) || (status is CalculationIndetermined)) + { + continue; + } + + _status = status; + return; + } + + // Got all the way through + // So we're running because we had vectors passed to us. + if (!(_status is CalculationRunning)) + { + _status = new CalculationRunning(); + } + } + + /// + /// Gets the current calculation status. + /// + public ICalculationStatus Status + { + get + { + return _status; + } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void ResetToPrecalculationState() + { + // Indicate that we're no longer cancelled. + _wasIterationCancelled = false; + + // Reset the status. + _status = DefaultStatus; + + // Reset the stop-criteria + foreach (var stopCriterium in _stopCriterias.Select(pair => pair.Value)) + { + stopCriterium.ResetToPrecalculationState(); + } + } + + /// + /// Creates a deep clone of the current iterator. + /// + /// The deep clone of the current iterator. + public IIterator Clone() + { + var stopCriteria = _stopCriterias.Select(pair => pair.Value).Select(stopCriterium => (IIterationStopCriterium)stopCriterium.Clone()).ToList(); + return new Iterator(stopCriteria); + } + + #if !SILVERLIGHT + /// + /// Creates a deep clone of the current iterator. + /// + /// The deep clone of the current iterator. + object ICloneable.Clone() + { + return Clone(); + } + #endif + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Diagonal.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Diagonal.cs new file mode 100644 index 00000000..9dccb70a --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Diagonal.cs @@ -0,0 +1,148 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Preconditioners +{ + using System; + using Properties; + + /// + /// A diagonal preconditioner. The preconditioner uses the inverse + /// of the matrix diagonal as preconditioning values. + /// + public sealed class Diagonal : IPreConditioner + { + /// + /// The inverse of the matrix diagonal. + /// + private double[] _inverseDiagonals; + + /// + /// Returns the decomposed matrix diagonal. + /// + /// The matrix diagonal. + internal DiagonalMatrix DiagonalEntries() + { + var result = new DiagonalMatrix(_inverseDiagonals.Length); + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + result[i, i] = 1 / _inverseDiagonals[i]; + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The upon which this preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + _inverseDiagonals = new double[matrix.RowCount]; + for (var i = 0; i < matrix.RowCount; i++) + { + _inverseDiagonals[i] = 1 / matrix[i, i]; + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. + public Vector Approximate(Vector rhs) + { + if (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (_inverseDiagonals == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if (rhs.Count != _inverseDiagonals.Length) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rhs"); + } + + Vector result = new DenseVector(rhs.Count); + Approximate(rhs, result); + return result; + } + + /// + /// 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 (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (lhs == null) + { + throw new ArgumentNullException("lhs"); + } + + if (_inverseDiagonals == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _inverseDiagonals.Length)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rhs"); + } + + for (var i = 0; i < _inverseDiagonals.Length; i++) + { + lhs[i] = rhs[i] * _inverseDiagonals[i]; + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IPreConditioner.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IPreConditioner.cs new file mode 100644 index 00000000..e6534041 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IPreConditioner.cs @@ -0,0 +1,73 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Preconditioners +{ + /// + /// The base interface for preconditioner classes. + /// + /// + /// + /// Preconditioners are used by iterative solvers to improve the convergence + /// speed of the solving process. Increase in convergence speed + /// is related to the number of iterations necessary to get a converged solution. + /// So while in general the use of a preconditioner means that the iterative + /// solver will perform fewer iterations it does not guarantee that the actual + /// solution time decreases given that some preconditioners can be expensive to + /// setup and run. + /// + /// + /// Note that in general changes to the matrix will invalidate the preconditioner + /// if the changes occur after creating the preconditioner. + /// + /// + public interface IPreConditioner + { + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix on which the preconditioner is based. + void Initialize(Matrix matrix); + + /// + /// Approximates the solution to the matrix equation Mx = b. + /// + /// The right hand side vector. + /// The left hand side vector. + Vector Approximate(Vector rhs); + + /// + /// Approximates the solution to the matrix equation Mx = b. + /// + /// The right hand side vector. + /// The left hand side vector. Also known as the result vector. + void Approximate(Vector rhs, Vector lhs); + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Ilutp.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Ilutp.cs new file mode 100644 index 00000000..8938e19b --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/Ilutp.cs @@ -0,0 +1,727 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Preconditioners +{ + using System; + using System.Collections.Generic; + using Properties; + + /// + /// 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 Ilutp : 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. + /// + private SparseMatrix _upper; + + /// + /// The decomposed lower triangular matrix. + /// + private SparseMatrix _lower; + + /// + /// The array containing the pivot values. + /// + private int[] _pivots; + + /// + /// The fill level. + /// + private double _fillLevel = DefaultFillLevel; + + /// + /// The drop tolerance. + /// + private double _dropTolerance = DefaultDropTolerance; + + /// + /// The pivot tolerance. + /// + private double _pivotTolerance; + + /// + /// Initializes a new instance of the class with the default settings. + /// + public Ilutp() + { + } + + /// + /// 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 Ilutp(double fillLevel, double dropTolerance, double pivotTolerance) + { + if (fillLevel < 0) + { + throw new ArgumentOutOfRangeException("fillLevel"); + } + + if (dropTolerance < 0) + { + throw new ArgumentOutOfRangeException("dropTolerance"); + } + + if (pivotTolerance < 0) + { + throw new ArgumentOutOfRangeException("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 + { + return _fillLevel; + } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("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 + { + return _dropTolerance; + } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("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 + { + return _pivotTolerance; + } + + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("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("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + var sparseMatrix = (matrix is SparseMatrix) ? matrix as SparseMatrix : new SparseMatrix(matrix.ToArray()); + + // 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; + } + + Vector workVector = new DenseVector(sparseMatrix.RowCount); + Vector 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.Norm(Double.PositiveInfinity); + + // 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 (Math.Abs(workVector[j]) < _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]); + workVector.Subtract(rowVector); + } + } + } + + // for j = i, .. ,n + for (var j = i; j < sparseMatrix.RowCount; j++) + { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + if (Math.Abs(workVector[j]) <= _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 (Math.Abs(workVector[i]) < _pivotTolerance * Math.Abs(workVector[indexSorting[0]])) + { + // 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 + private 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 + private bool PivotMapFound(Dictionary knownPivots, int currentItem) + { + if (knownPivots.ContainsKey(_pivots[currentItem])) + { + if (knownPivots[_pivots[currentItem]].Equals(currentItem)) + { + return true; + } + } + + if (knownPivots.ContainsKey(currentItem)) + { + if (knownPivots[currentItem].Equals(_pivots[currentItem])) + { + return true; + } + } + + return false; + } + + /// + /// Swap columns in the + /// + /// Source . + /// First column index to swap + /// Second column index to swap + private 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 indicies to + /// + /// Start sort form + /// Sort till upper bound + /// Array with sorted vector indicies + /// Source + private 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. + public Vector Approximate(Vector rhs) + { + if (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (_upper == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if (rhs.Count != _upper.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rhs"); + } + + Vector result = new DenseVector(rhs.Count); + Approximate(rhs, result); + return result; + } + + /// + /// 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 (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (lhs == null) + { + throw new ArgumentNullException("lhs"); + } + + if (_upper == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _upper.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rhs"); + } + + // Solve equation here + // Pivot(vector, result); + // Solve L*Y = B(piv,:) + Vector rowValues = new DenseVector(_lower.RowCount); + for (var i = 0; i < _lower.RowCount; i++) + { + _lower.Row(i, rowValues); + + var sum = 0.0; + 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 = 0.0; + 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 accoring to internal pivot array + /// + /// Source . + /// Result after pivoting. + private void Pivot(Vector vector, Vector result) + { + for (var i = 0; i < _pivots.Length; i++) + { + result[i] = vector[_pivots[i]]; + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IlutpElementSorter.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IlutpElementSorter.cs new file mode 100644 index 00000000..802314a1 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IlutpElementSorter.cs @@ -0,0 +1,225 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Preconditioners +{ + /// + /// 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 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 indicies + /// + /// Root position + /// Lenght of + /// Indicies 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 indicies + /// + /// Indicies of + /// Target + /// Root position + /// Lenght of + private static void SiftDoubleIndices(int[] sortedIndices, Vector values, int begin, int count) + { + var root = begin; + int child; + + while (root * 2 < count) + { + child = root * 2; + if ((child < count - 1) && (values[sortedIndices[child]] > values[sortedIndices[child + 1]])) + { + child += 1; + } + + if (values[sortedIndices[root]] <= values[sortedIndices[child]]) + { + 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 + /// Lenght 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 + /// Lenght of + private static void Sift(int[] values, int start, int count) + { + var root = start; + int child; + + while (root * 2 < count) + { + 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 exchanghe + /// Second value to exchanghe + private static void Exchange(int[] values, int first, int second) + { + var t = values[first]; + values[first] = values[second]; + values[second] = t; + } + } +} \ No newline at end of file diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IncompleteLU.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IncompleteLU.cs new file mode 100644 index 00000000..3f0e55bf --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/IncompleteLU.cs @@ -0,0 +1,258 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Preconditioners +{ + using System; + using Properties; + + /// + /// An incomplete, level 0, LU factorization preconditioner. + /// + /// + /// The ILU(0) algorithm was taken from:
+ /// Iterative methods for sparse linear systems
+ /// Yousef Saad
+ /// Algorithm is described in Chapter 10, section 10.3.2, page 275
+ ///
+ public sealed class IncompleteLU : IPreConditioner + { + /// + /// The matrix holding the lower (L) and upper (U) matrices. The + /// decomposition matrices are combined to reduce storage. + /// + private SparseMatrix _decompositionLU; + + /// + /// Returns the upper triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the upper triagonal elements. + internal Matrix UpperTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = i; j < _decompositionLU.ColumnCount; j++) + { + result[i, j] = _decompositionLU[i, j]; + } + } + + return result; + } + + /// + /// Returns the lower triagonal matrix that was created during the LU decomposition. + /// + /// A new matrix containing the lower triagonal elements. + internal Matrix LowerTriangle() + { + var result = new SparseMatrix(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var j = 0; j <= i; j++) + { + if (i == j) + { + result[i, j] = 1.0; + } + else + { + result[i, j] = _decompositionLU[i, j]; + } + } + } + + return result; + } + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// The matrix upon which the preconditioner is based. + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + _decompositionLU = new SparseMatrix(matrix.ToArray()); + + // M == A + // for i = 2, ... , n do + // for k = 1, .... , i - 1 do + // if (i,k) == NZ(Z) then + // compute z(i,k) = z(i,k) / z(k,k); + // for j = k + 1, ...., n do + // if (i,j) == NZ(Z) then + // compute z(i,j) = z(i,j) - z(i,k) * z(k,j) + // end + // end + // end + // end + // end + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + for (var k = 0; k < i; k++) + { + if (_decompositionLU[i, k] != 0.0) + { + var t = _decompositionLU[i, k] / _decompositionLU[k, k]; + _decompositionLU[i, k] = t; + if (_decompositionLU[k, i] != 0.0) + { + _decompositionLU[i, i] = _decompositionLU[i, i] - (t * _decompositionLU[k, i]); + } + + for (var j = k + 1; j < _decompositionLU.RowCount; j++) + { + if (j == i) + { + continue; + } + + if (_decompositionLU[i, j] != 0.0) + { + _decompositionLU[i, j] = _decompositionLU[i, j] - (t * _decompositionLU[k, j]); + } + } + } + } + } + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. + public Vector Approximate(Vector rhs) + { + if (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (_decompositionLU == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if (rhs.Count != _decompositionLU.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rhs"); + } + + Vector result = new DenseVector(rhs.Count); + Approximate(rhs, result); + return result; + } + + /// + /// 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 (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (lhs == null) + { + throw new ArgumentNullException("lhs"); + } + + if (_decompositionLU == null) + { + throw new ArgumentException(Resources.ArgumentMatrixDoesNotExist); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _decompositionLU.RowCount)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Solve: + // Lz = y + // Which gives + // for (int i = 1; i < matrix.RowLength; i++) + // { + // z_i = l_ii^-1 * (y_i - SUM_(j -1; i--) + // { + // x_i = u_ii^-1 * (z_i - SUM_(j > i) u_ij * x_j) + // } + for (var i = _decompositionLU.RowCount - 1; i > -1; i--) + { + _decompositionLU.Row(i, rowValues); + + var sum = 0.0; + for (var j = _decompositionLU.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/UnitPreconditioner.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/UnitPreconditioner.cs new file mode 100644 index 00000000..9f586f76 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Preconditioners/UnitPreconditioner.cs @@ -0,0 +1,136 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Preconditioners +{ + using System; + using Properties; + + /// + /// A unit preconditioner. This preconditioner does not actually do anything + /// it is only used when running an without + /// a preconditioner. + /// + internal sealed class UnitPreconditioner : IPreConditioner + { + /// + /// The coefficient matrix on which this preconditioner operates. + /// Is used to check dimensions on the different vectors that are processed. + /// + private int _size; + + /// + /// Initializes the preconditioner and loads the internal data structures. + /// + /// + /// The matrix upon which the preconditioner is based. + /// + /// If is . + /// If is not a square matrix. + public void Initialize(Matrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare, "matrix"); + } + + _size = matrix.RowCount; + } + + /// + /// 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. + /// If is . + /// If is . + /// + /// + /// If and do not have the same size. + /// + /// + /// - or - + /// + /// + /// If the size of is different the number of rows of the coefficient matrix. + /// + /// + public void Approximate(Vector rhs, Vector lhs) + { + if (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (lhs == null) + { + throw new ArgumentNullException("lhs"); + } + + if ((lhs.Count != rhs.Count) || (lhs.Count != _size)) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + rhs.CopyTo(lhs); + } + + /// + /// Approximates the solution to the matrix equation Ax = b. + /// + /// The right hand side vector. + /// The left hand side vector. + /// If is . + /// + /// If the size of is different the number of rows of the coefficient matrix. + /// + public Vector Approximate(Vector rhs) + { + if (rhs == null) + { + throw new ArgumentNullException("rhs"); + } + + if (rhs.Count != _size) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + Vector result = new DenseVector(rhs.Count); + Approximate(rhs, result); + return result; + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationCancelled.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationCancelled.cs new file mode 100644 index 00000000..6377a79f --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationCancelled.cs @@ -0,0 +1,49 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Indicates that a calculation was cancelled by the user. + /// + public struct CalculationCancelled : ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + public bool TerminatesCalculation + { + get + { + return true; + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationConverged.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationConverged.cs new file mode 100644 index 00000000..a835e4d7 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationConverged.cs @@ -0,0 +1,51 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Indicates that a calculation has converged to the desired convergence levels. + /// + public struct CalculationConverged : ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + public bool TerminatesCalculation + { + get + { + return true; + } + } + + // TODO: CalculationConverged: Should we put the achieved residuals and convergence levels on here? + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationDiverged.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationDiverged.cs new file mode 100644 index 00000000..c2be337a --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationDiverged.cs @@ -0,0 +1,51 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Indicates that the calculation diverged. + /// + public struct CalculationDiverged : ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + public bool TerminatesCalculation + { + get + { + return true; + } + } + + // TODO: CalculationDiverged - Should we put the residuals on here? + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationFailure.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationFailure.cs new file mode 100644 index 00000000..344a1d7e --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationFailure.cs @@ -0,0 +1,51 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Indicates that a calculation has failed for some reason. + /// + public struct CalculationFailure : ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + public bool TerminatesCalculation + { + get + { + return true; + } + } + + // TODO: CalcuationFailure - Indicate why the calculation has failed? + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationIndetermined.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationIndetermined.cs new file mode 100644 index 00000000..cd5e58be --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationIndetermined.cs @@ -0,0 +1,49 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Indicates that the state of the calculation is indetermined, not started or stopped. + /// + public struct CalculationIndetermined : ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + public bool TerminatesCalculation + { + get + { + return false; + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationRunning.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationRunning.cs new file mode 100644 index 00000000..19dbc8a9 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationRunning.cs @@ -0,0 +1,51 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Indicates that the calculation is running and no results are yet known. + /// + public struct CalculationRunning : ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + public bool TerminatesCalculation + { + get + { + return false; + } + } + + // TODO: CalculationRunning - Get current residuals? + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationStoppedWithoutConvergence.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationStoppedWithoutConvergence.cs new file mode 100644 index 00000000..e4a9c799 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/CalculationStoppedWithoutConvergence.cs @@ -0,0 +1,52 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Indicates that the calculation has been stopped due to reaching the stopping + /// limits, but that convergence was not achieved. + /// + public struct CalculationStoppedWithoutConvergence : ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + public bool TerminatesCalculation + { + get + { + return true; + } + } + + // TODO: Indicate which stopping limit was reached? + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Status/ICalculationStatus.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Status/ICalculationStatus.cs new file mode 100644 index 00000000..97b867e6 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Status/ICalculationStatus.cs @@ -0,0 +1,43 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Status +{ + /// + /// Defines the base interface for calculation status objects. + /// + public interface ICalculationStatus + { + /// + /// Gets a value indicating whether current status warrants stopping the calculation. + /// + bool TerminatesCalculation { get; } + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/DivergenceStopCriterium.cs b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/DivergenceStopCriterium.cs new file mode 100644 index 00000000..98426ade --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/DivergenceStopCriterium.cs @@ -0,0 +1,391 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using Status; + + /// + /// Monitors an iterative calculation for signs of divergence. + /// + public sealed class DivergenceStopCriterium : IIterationStopCriterium + { + /// + /// Default value for the maximum relative increase that the + /// residual may experience before a divergence warning is issued. + /// + public const double DefaultMaximumRelativeIncrease = 0.08; + + /// + /// Default value for the minimum number of iterations over which + /// the residual must grow before a divergence warning is issued. + /// + public const int DefaultMinimumNumberOfIterations = 10; + + /// + /// Defines the default last iteration number. Set to -1 because iterations normally + /// start at 0. + /// + private const int DefaultLastIterationNumber = -1; + + /// + /// The default status. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The maximum relative increase the residual may experience without triggering a divergence warning. + /// + private double _maximumRelativeIncrease; + + /// + /// The number of iterations over which a residual increase should be tracked before issuing a divergence warning. + /// + private int _minimumNumberOfIterations; + + /// + /// The status of the calculation + /// + private ICalculationStatus _status = DefaultStatus; + + /// + /// The array that holds the tracking information. + /// + private double[] _residualHistory; + + /// + /// The iteration number of the last iteration. + /// + private int _lastIteration = DefaultLastIterationNumber; + + /// + /// Initializes a new instance of the class with the default maximum + /// relative increase and the default minimum number of tracking iterations. + /// + public DivergenceStopCriterium() : this(DefaultMaximumRelativeIncrease, DefaultMinimumNumberOfIterations) + { + } + + /// + /// Initializes a new instance of the class with the specified maximum + /// relative increase and the default minimum number of tracking iterations. + /// + /// The maximum relative increase that the residual may experience before a divergence warning is issued. + public DivergenceStopCriterium(double maximumRelativeIncrease) : this(maximumRelativeIncrease, DefaultMinimumNumberOfIterations) + { + } + + /// + /// Initializes a new instance of the class with the default maximum + /// relative increase and the specified minimum number of tracking iterations. + /// + /// The minimum number of iterations over which the residual must grow before a divergence warning is issued. + public DivergenceStopCriterium(int minimumIterations) : this(DefaultMinimumNumberOfIterations, minimumIterations) + { + } + + /// + /// Initializes a new instance of the class with the specified maximum + /// relative increase and the specified minimum number of tracking iterations. + /// + /// The maximum relative increase that the residual may experience before a divergence warning is issued. + /// The minimum number of iterations over which the residual must grow before a divergence warning is issued. + public DivergenceStopCriterium(double maximumRelativeIncrease, int minimumIterations) + { + if (maximumRelativeIncrease <= 0) + { + throw new ArgumentOutOfRangeException("maximumRelativeIncrease"); + } + + // There must be at least three iterations otherwise we can't calculate the relative increase + if (minimumIterations < 3) + { + throw new ArgumentOutOfRangeException("minimumIterations"); + } + + _maximumRelativeIncrease = maximumRelativeIncrease; + _minimumNumberOfIterations = minimumIterations; + } + + /// + /// Gets or sets the maximum relative increase that the residual may experience before a divergence warning is issued. + /// + /// Thrown if the Maximum is set to zero or below. + public double MaximumRelativeIncrease + { + [DebuggerStepThrough] + get + { + return _maximumRelativeIncrease; + } + + [DebuggerStepThrough] + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException("value"); + } + + _maximumRelativeIncrease = value; + } + } + + /// + /// Returns the maximum relative increase to the default. + /// + public void ResetMaximumRelativeIncreaseToDefault() + { + _maximumRelativeIncrease = DefaultMaximumRelativeIncrease; + } + + /// + /// Gets or sets the minimum number of iterations over which the residual must grow before + /// issuing a divergence warning. + /// + /// Thrown if the value is set to less than one. + public int MinimumNumberOfIterations + { + [DebuggerStepThrough] + get + { + return _minimumNumberOfIterations; + } + + [DebuggerStepThrough] + set + { + // There must be at least three iterations otherwise we can't calculate + // the relative increase + if (value < 3) + { + throw new ArgumentOutOfRangeException("value"); + } + + _minimumNumberOfIterations = value; + } + } + + /// + /// Returns the minimum number of iterations to the default. + /// + public void ResetNumberOfIterationsToDefault() + { + _minimumNumberOfIterations = DefaultMinimumNumberOfIterations; + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException("iterationNumber"); + } + + if (residualVector == null) + { + throw new ArgumentNullException("residualVector"); + } + + if (_lastIteration >= iterationNumber) + { + // We have already stored the actual last iteration number + // For now do nothing. We only care about the next step. + return; + } + + if ((_residualHistory == null) || (_residualHistory.Length != RequiredHistoryLength)) + { + _residualHistory = new double[RequiredHistoryLength]; + } + + // We always track the residual. + // Move the old versions one element up in the array. + for (var i = 1; i < _residualHistory.Length; i++) + { + _residualHistory[i - 1] = _residualHistory[i]; + } + + // Store the infinity norms of both the solution and residual vectors + // These values will be used to calculate the relative drop in residuals later on. + _residualHistory[_residualHistory.Length - 1] = residualVector.Norm(Double.PositiveInfinity); + + // Check if we have NaN's. If so we've gone way beyond normal divergence. + // Stop the iteration. + if (double.IsNaN(_residualHistory[_residualHistory.Length - 1])) + { + SetStatusToDiverged(); + return; + } + + // Check if we are diverging and if so set the status + if (IsDiverging()) + { + SetStatusToDiverged(); + } + else + { + SetStatusToRunning(); + } + + _lastIteration = iterationNumber; + } + + /// + /// Detect if solution is diverging + /// + /// true if diverging, otherwise false + private bool IsDiverging() + { + // Run for each variable + for (var i = 1; i < _residualHistory.Length; i++) + { + var difference = _residualHistory[i] - _residualHistory[i - 1]; + + // Divergence is occurring if: + // - the last residual is larger than the previous one + // - the relative increase of the residual is larger than the setting allows + if ((difference < 0) || (_residualHistory[i - 1] * (1 + _maximumRelativeIncrease) >= _residualHistory[i])) + { + // No divergence taking place within the required number of iterations + // So reset and stop the iteration. There is no way we can get to the + // required number of iterations anymore. + return false; + } + } + + return true; + } + + /// + /// Gets required history lenght + /// + private int RequiredHistoryLength + { + [DebuggerStepThrough] + get + { + return _minimumNumberOfIterations + 1; + } + } + + /// + /// Set status to + /// + private void SetStatusToDiverged() + { + if (!(_status is CalculationDiverged)) + { + _status = new CalculationDiverged(); + } + } + + /// + /// Set status to + /// + private void SetStatusToRunning() + { + if (!(_status is CalculationRunning)) + { + _status = new CalculationRunning(); + } + } + + /// + /// Gets the current calculation status. + /// + public ICalculationStatus Status + { + [DebuggerStepThrough] + get + { + return _status; + } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void ResetToPrecalculationState() + { + _status = DefaultStatus; + _lastIteration = DefaultLastIterationNumber; + _residualHistory = null; + } + + /// + /// Gets the which indicates what sort of stop criterium this + /// monitors. + /// + /// Returns . + public StopLevel StopLevel + { + [DebuggerStepThrough] + get + { + return StopLevel.Divergence; + } + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterium Clone() + { + return new DivergenceStopCriterium(_maximumRelativeIncrease, _minimumNumberOfIterations); + } + +#if !SILVERLIGHT + /// + /// Clone this object + /// + /// Object clone + object ICloneable.Clone() + { + return Clone(); + } +#endif + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/FailureStopCriterium.cs b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/FailureStopCriterium.cs new file mode 100644 index 00000000..8e68a180 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/FailureStopCriterium.cs @@ -0,0 +1,199 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using Properties; + using Status; + + /// + /// Defines an that monitors residuals for NaN's. + /// + public sealed class FailureStopCriterium : IIterationStopCriterium + { + /// + /// Defines the default last iteration number. Set to -1 because iterations normally + /// start at 0. + /// + private const int DefaultLastIterationNumber = -1; + + /// + /// The default status. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The status of the calculation + /// + private ICalculationStatus _status = DefaultStatus; + + /// + /// The iteration number of the last iteration. + /// + private int _lastIteration = DefaultLastIterationNumber; + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException("iterationNumber"); + } + + if (solutionVector == null) + { + throw new ArgumentNullException("solutionVector"); + } + + if (residualVector == null) + { + throw new ArgumentNullException("residualVector"); + } + + if (solutionVector.Count != residualVector.Count) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength); + } + + if (_lastIteration >= iterationNumber) + { + // We have already stored the actual last iteration number + // For now do nothing. We only care about the next step. + return; + } + + // Store the infinity norms of both the solution and residual vectors + var residualNorm = residualVector.Norm(Double.PositiveInfinity); + var solutionNorm = solutionVector.Norm(Double.PositiveInfinity); + + if (Double.IsNaN(solutionNorm) || Double.IsNaN(residualNorm)) + { + SetStatusToFailed(); + } + else + { + SetStatusToRunning(); + } + + _lastIteration = iterationNumber; + } + + /// + /// Set status to + /// + private void SetStatusToFailed() + { + if (!(_status is CalculationFailure)) + { + _status = new CalculationFailure(); + } + } + + /// + /// Set status to + /// + private void SetStatusToRunning() + { + if (!(_status is CalculationRunning)) + { + _status = new CalculationRunning(); + } + } + + /// + /// Gets the current calculation status. + /// + public ICalculationStatus Status + { + [DebuggerStepThrough] + get + { + return _status; + } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void ResetToPrecalculationState() + { + _status = DefaultStatus; + _lastIteration = DefaultLastIterationNumber; + } + + /// + /// Gets the which indicates what sort of stop criterium this + /// monitors. + /// + /// Returns . + public StopLevel StopLevel + { + [DebuggerStepThrough] + get + { + return StopLevel.CalculationFailure; + } + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterium Clone() + { + return new FailureStopCriterium(); + } + +#if !SILVERLIGHT + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + object ICloneable.Clone() + { + return Clone(); + } +#endif + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IIterationStopCriterium.cs b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IIterationStopCriterium.cs new file mode 100644 index 00000000..6355b190 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IIterationStopCriterium.cs @@ -0,0 +1,79 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.StopCriterium +{ + using System; + using Status; + + /// + /// The base interface for classes that provide stop criteria for iterative calculations. + /// + public interface IIterationStopCriterium +#if !SILVERLIGHT + : ICloneable +#endif + { + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Status is set to Status field of current object. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector); + + /// + /// Gets the current calculation status. + /// + /// is not a legal value. Status should be set in implementation. + ICalculationStatus Status { get; } + + /// + /// Resets the to the pre-calculation state. + /// + /// To implementers: Invoking this method should not clear the user defined + /// property values, only the state that is used to track the progress of the + /// calculation. + void ResetToPrecalculationState(); + + /// + /// Gets the which indicates what sort of stop criterium this + /// monitors. + /// + StopLevel StopLevel { get; } + +#if SILVERLIGHT + IIterationStopCriterium Clone(); +#endif + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IterationCountStopCriterium.cs b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IterationCountStopCriterium.cs new file mode 100644 index 00000000..b7250c0d --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/IterationCountStopCriterium.cs @@ -0,0 +1,225 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using Status; + + /// + /// Defines an that monitors the numbers of iteration + /// steps as stop criterium. + /// + public sealed class IterationCountStopCriterium : IIterationStopCriterium + { + /// + /// The default value for the maximum number of iterations the process is allowed + /// to perform. + /// + public const int DefaultMaximumNumberOfIterations = 1000; + + /// + /// The default status. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The maximum number of iterations the calculation is allowed to perform. + /// + private int _maximumNumberOfIterations; + + /// + /// The status of the calculation + /// + private ICalculationStatus _status = DefaultStatus; + + /// + /// Initializes a new instance of the class with the default maximum + /// number of iterations. + /// + public IterationCountStopCriterium() : this(DefaultMaximumNumberOfIterations) + { + } + + /// + /// Initializes a new instance of the class with the specified maximum + /// number of iterations. + /// + /// The maximum number of iterations the calculation is allowed to perform. + public IterationCountStopCriterium(int maximumNumberOfIterations) + { + if (maximumNumberOfIterations < 1) + { + throw new ArgumentOutOfRangeException("maximumNumberOfIterations"); + } + + _maximumNumberOfIterations = maximumNumberOfIterations; + } + + /// + /// Gets or sets the maximum number of iterations the calculation is allowed to perform. + /// + /// Thrown if the Maximum is set to a negative value. + public int MaximumNumberOfIterations + { + [DebuggerStepThrough] + get + { + return _maximumNumberOfIterations; + } + + [DebuggerStepThrough] + set + { + if (value < 1) + { + throw new ArgumentOutOfRangeException("value"); + } + + _maximumNumberOfIterations = value; + } + } + + /// + /// Returns the maximum number of iterations to the default. + /// + public void ResetMaximumNumberOfIterationsToDefault() + { + _maximumNumberOfIterations = DefaultMaximumNumberOfIterations; + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException("iterationNumber"); + } + + if (iterationNumber >= _maximumNumberOfIterations) + { + SetStatusToFinished(); + } + else + { + SetStatusToRunning(); + } + } + + /// + /// Set status to + /// + private void SetStatusToFinished() + { + if (!(_status is CalculationStoppedWithoutConvergence)) + { + _status = new CalculationStoppedWithoutConvergence(); + } + } + + /// + /// Set status to + /// + private void SetStatusToRunning() + { + if (!(_status is CalculationRunning)) + { + _status = new CalculationRunning(); + } + } + + /// + /// Gets the current calculation status. + /// + public ICalculationStatus Status + { + [DebuggerStepThrough] + get + { + return _status; + } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void ResetToPrecalculationState() + { + _status = DefaultStatus; + } + + /// + /// Gets the which indicates what sort of stop criterium this + /// monitors. + /// + /// Returns . + public StopLevel StopLevel + { + [DebuggerStepThrough] + get + { + return StopLevel.StoppedWithoutConvergence; + } + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterium Clone() + { + return new IterationCountStopCriterium(_maximumNumberOfIterations); + } + +#if !SILVERLIGHT + /// + /// Clones the current and its settings. + /// + /// A new instance of the object. + object ICloneable.Clone() + { + return Clone(); + } +#endif + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/ResidualStopCriterium.cs b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/ResidualStopCriterium.cs new file mode 100644 index 00000000..aac8a500 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/ResidualStopCriterium.cs @@ -0,0 +1,407 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using Properties; + using Status; + + /// + /// Defines an that monitors residuals as stop criterium. + /// + public sealed class ResidualStopCriterium : IIterationStopCriterium + { + /// + /// The default value for the maximum value of the residual. + /// + public const double DefaultMaximumResidual = 1e-12; + + /// + /// The default value for the minimum number of iterations. + /// + public const int DefaultMinimumIterationsBelowMaximum = 0; + + /// + /// Defines the default last iteration number. Set to -1 because iterations normally start at 0. + /// + private const int DefaultLastIterationNumber = -1; + + /// + /// The default status. + /// + private static readonly ICalculationStatus DefaultStatus = new CalculationIndetermined(); + + /// + /// The maximum value for the residual below which the calculation is considered converged. + /// + private double _maximum; + + /// + /// The minimum number of iterations for which the residual has to be below the maximum before + /// the calculation is considered converged. + /// + private int _minimumIterationsBelowMaximum; + + /// + /// The status of the calculation + /// + private ICalculationStatus _status = DefaultStatus; + + /// + /// The number of iterations since the residuals got below the maximum. + /// + private int _iterationCount; + + /// + /// The iteration number of the last iteration. + /// + private int _lastIteration = DefaultLastIterationNumber; + + /// + /// Initializes a new instance of the class with the default maximum + /// residual and the default minimum number of iterations. + /// + public ResidualStopCriterium() : this(DefaultMaximumResidual, DefaultMinimumIterationsBelowMaximum) + { + } + + /// + /// Initializes a new instance of the class with the specified + /// maximum residual and the default minimum number of iterations. + /// + /// The maximum value for the residual below which the calculation is considered converged. + public ResidualStopCriterium(double maximum) : this(maximum, DefaultMinimumIterationsBelowMaximum) + { + } + + /// + /// Initializes a new instance of the class with the default maximum residual + /// and specified minimum number of iterations. + /// + /// + /// The minimum number of iterations for which the residual has to be below the maximum before + /// the calculation is considered converged. + /// + public ResidualStopCriterium(int minimumIterationsBelowMaximum) : this(DefaultMaximumResidual, minimumIterationsBelowMaximum) + { + } + + /// + /// Initializes a new instance of the class with the specified + /// maximum residual and minimum number of iterations. + /// + /// + /// The maximum value for the residual below which the calculation is considered converged. + /// + /// + /// The minimum number of iterations for which the residual has to be below the maximum before + /// the calculation is considered converged. + /// + public ResidualStopCriterium(double maximum, int minimumIterationsBelowMaximum) + { + if (maximum < 0) + { + throw new ArgumentOutOfRangeException("maximum"); + } + + if (minimumIterationsBelowMaximum < 0) + { + throw new ArgumentOutOfRangeException("minimumIterationsBelowMaximum"); + } + + _maximum = maximum; + _minimumIterationsBelowMaximum = minimumIterationsBelowMaximum; + } + + /// + /// Gets or sets the maximum value for the residual below which the calculation is considered + /// converged. + /// + /// Thrown if the Maximum is set to a negative value. + public double Maximum + { + [DebuggerStepThrough] + get + { + return _maximum; + } + + [DebuggerStepThrough] + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("value"); + } + + _maximum = value; + } + } + + /// + /// Returns the maximum residual to the default. + /// + public void ResetMaximumResidualToDefault() + { + _maximum = DefaultMaximumResidual; + } + + /// + /// Gets or sets the minimum number of iterations for which the residual has to be + /// below the maximum before the calculation is considered converged. + /// + /// Thrown if the BelowMaximumFor is set to a value less than 1. + public int MinimumIterationsBelowMaximum + { + [DebuggerStepThrough] + get + { + return _minimumIterationsBelowMaximum; + } + + [DebuggerStepThrough] + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException("value"); + } + + _minimumIterationsBelowMaximum = value; + } + } + + /// + /// Returns the minimum number of iterations to the default. + /// + public void ResetMinimumIterationsBelowMaximumToDefault() + { + _minimumIterationsBelowMaximum = DefaultMinimumIterationsBelowMaximum; + } + + /// + /// Determines the status of the iterative calculation based on the stop criteria stored + /// by the current . Result is set into Status field. + /// + /// The number of iterations that have passed so far. + /// The vector containing the current solution values. + /// The right hand side vector. + /// The vector containing the current residual vectors. + /// + /// The individual stop criteria may internally track the progress of the calculation based + /// on the invocation of this method. Therefore this method should only be called if the + /// calculation has moved forwards at least one step. + /// + public void DetermineStatus(int iterationNumber, Vector solutionVector, Vector sourceVector, Vector residualVector) + { + if (iterationNumber < 0) + { + throw new ArgumentOutOfRangeException("iterationNumber"); + } + + if (solutionVector == null) + { + throw new ArgumentNullException("solutionVector"); + } + + if (sourceVector == null) + { + throw new ArgumentNullException("sourceVector"); + } + + if (residualVector == null) + { + throw new ArgumentNullException("residualVector"); + } + + if (solutionVector.Count != sourceVector.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "sourceVector"); + } + + if (solutionVector.Count != residualVector.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "residualVector"); + } + + // Store the infinity norms of both the solution and residual vectors + // These values will be used to calculate the relative drop in residuals + // later on. + var residualNorm = residualVector.Norm(Double.PositiveInfinity); + + // Check the residuals by calculating: + // ||r_i|| <= stop_tol * ||b|| + var stopCriterium = ComputeStopCriterium(sourceVector.Norm(Double.PositiveInfinity)); + + // First check that we have real numbers not NaN's. + // NaN's can occur when the iterative process diverges so we + // stop if that is the case. + if (double.IsNaN(stopCriterium) || double.IsNaN(residualNorm)) + { + _iterationCount = 0; + SetStatusToDiverged(); + return; + } + + // ||r_i|| <= stop_tol * ||b|| + // Stop the calculation if it's clearly smaller than the tolerance + var decimalMagnitude = Math.Abs(stopCriterium.Magnitude()) + 1; + if (residualNorm.IsSmallerWithDecimalPlaces(stopCriterium, decimalMagnitude)) + { + if (_lastIteration <= iterationNumber) + { + _iterationCount = iterationNumber - _lastIteration; + if (_iterationCount >= _minimumIterationsBelowMaximum) + { + SetStatusToConverged(); + } + else + { + SetStatusToRunning(); + } + } + } + else + { + _iterationCount = 0; + SetStatusToRunning(); + } + + _lastIteration = iterationNumber; + } + + /// + /// Calculate stop criterium + /// + /// Solution vector norm + /// Criterium value + private double ComputeStopCriterium(double solutionNorm) + { + // This is criterium 1 from Templates for the solution of linear systems. + // The problem with this criterium is that it's not limiting enough. For now + // we won't use it. Later on we might get back to it. + // return mMaximumResidual * (System.Math.Abs(mMatrixNorm) * System.Math.Abs(solutionNorm) + System.Math.Abs(mVectorNorm)); + + // For now use criterium 2 from Templates for the solution of linear systems. See page 60. + return _maximum * Math.Abs(solutionNorm); + } + + /// + /// Set status to + /// + private void SetStatusToDiverged() + { + if (!(_status is CalculationDiverged)) + { + _status = new CalculationDiverged(); + } + } + + /// + /// Set status to + /// + private void SetStatusToConverged() + { + if (!(_status is CalculationConverged)) + { + _status = new CalculationConverged(); + } + } + + /// + /// Set status to + /// + private void SetStatusToRunning() + { + if (!(_status is CalculationRunning)) + { + _status = new CalculationRunning(); + } + } + + /// + /// Gets the current calculation status. + /// + public ICalculationStatus Status + { + [DebuggerStepThrough] + get + { + return _status; + } + } + + /// + /// Resets the to the pre-calculation state. + /// + public void ResetToPrecalculationState() + { + _status = DefaultStatus; + _iterationCount = 0; + _lastIteration = DefaultLastIterationNumber; + } + + /// + /// Gets the which indicates what sort of stop criterium this + /// monitors. + /// + /// Returns . + public StopLevel StopLevel + { + [DebuggerStepThrough] + get + { + return StopLevel.Convergence; + } + } + + /// + /// Clones the current and its settings. + /// + /// A new instance of the class. + public IIterationStopCriterium Clone() + { + return new ResidualStopCriterium(_maximum, _minimumIterationsBelowMaximum); + } + +#if !SILVERLIGHT + /// + /// Clones the current and its settings. + /// + /// A new instance of the object. + object ICloneable.Clone() + { + return Clone(); + } +#endif + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/StopLevel.cs b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/StopLevel.cs new file mode 100644 index 00000000..ac3e0739 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Solvers/StopCriterium/StopLevel.cs @@ -0,0 +1,57 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// 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. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.StopCriterium +{ + /// + /// Indicates what an IIterationStopCriterium monitors for stop criteria. + /// + public enum StopLevel + { + /// + /// The monitors calculation failures in the + /// iterative calculation. + /// + CalculationFailure, + + /// + /// The monitors the calculation for signs of divergence. + /// + Divergence, + + /// + /// The guards the calculation against unlimited continuation + /// by monitoring user specified limits, e.g. the maximum number of iterations. + /// + StoppedWithoutConvergence, + + /// + /// The monitors the calculation for convergence, usually + /// based on the residuals of the calculation. + /// + Convergence + } +} diff --git a/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs b/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs index fb541ad7..bd73b1df 100644 --- a/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs +++ b/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs @@ -891,6 +891,71 @@ namespace MathNet.Numerics.LinearAlgebra.Double return ret; } + /// Calculates the Frobenius norm of this matrix. + /// The Frobenius norm of this matrix. + public override double FrobeniusNorm() + { + var transpose = (SparseMatrix)Transpose(); + var aat = this * transpose; + + var norm = 0.0; + + for (var i = 0; i < aat._rowIndex.Length; i++) + { + // Get the begin / end index for the current row + var startIndex = aat._rowIndex[i]; + var endIndex = i < aat._rowIndex.Length - 1 ? aat._rowIndex[i + 1] : aat.NonZerosCount; + + // Get the values for the current row + if (startIndex == endIndex) + { + // Begin and end are equal. There are no values in the row, Move to the next row + continue; + } + + for (var j = startIndex; j < endIndex; j++) + { + if (i == aat._columnIndices[j]) + { + norm += Math.Abs(aat._nonZeroValues[j]); + } + } + } + + norm = Math.Sqrt(norm); + return norm; + } + + /// Calculates the infinity norm of this matrix. + /// The infinity norm of this matrix. + public override double InfinityNorm() + { + var norm = 0.0; + for (var i = 0; i < _rowIndex.Length; i++) + { + // Get the begin / end index for the current row + var startIndex = _rowIndex[i]; + var endIndex = i < _rowIndex.Length - 1 ? _rowIndex[i + 1] : NonZerosCount; + + // Get the values for the current row + if (startIndex == endIndex) + { + // Begin and end are equal. There are no values in the row, Move to the next row + continue; + } + + var s = 0.0; + for (var j = startIndex; j < endIndex; j++) + { + s += Math.Abs(_nonZeroValues[j]); + } + + norm = Math.Max(norm, s); + } + + return norm; + } + /// /// Copies the requested row elements into a new . /// @@ -1184,46 +1249,43 @@ namespace MathNet.Numerics.LinearAlgebra.Double { var otherSparseMatrix = other as SparseMatrix; var resultSparseMatrix = result as SparseMatrix; - if (otherSparseMatrix == null || resultSparseMatrix == null) { base.Multiply(other, result); + return; } - else + + if (ColumnCount != otherSparseMatrix.RowCount) { - if (ColumnCount != otherSparseMatrix.RowCount) - { - throw new ArgumentException(Resources.ArgumentMatrixDimensions); - } + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } - if (resultSparseMatrix.RowCount != RowCount || resultSparseMatrix.ColumnCount != otherSparseMatrix.ColumnCount) + if (resultSparseMatrix.RowCount != RowCount || resultSparseMatrix.ColumnCount != otherSparseMatrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + resultSparseMatrix.Clear(); + var columnVector = new DenseVector(otherSparseMatrix.RowCount); + for (var row = 0; row < RowCount; row++) + { + // Get the begin / end index for the current row + var startIndex = _rowIndex[row]; + var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[row + 1] : NonZerosCount; + if (startIndex == endIndex) { - throw new ArgumentException(Resources.ArgumentMatrixDimensions); + continue; } - resultSparseMatrix.Clear(); - - var columnVector = new DenseVector(otherSparseMatrix.RowCount); - for (var row = 0; row < RowCount; row++) + for (var column = 0; column < otherSparseMatrix.ColumnCount; column++) { - // Get the begin / end index for the current row - var startIndex = _rowIndex[row]; - var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[row + 1] : NonZerosCount; - if (startIndex == endIndex) - { - continue; - } - - for (var column = 0; column < otherSparseMatrix.ColumnCount; column++) - { - // Multiply row of matrix A on column of matrix B - otherSparseMatrix.Column(column, columnVector); - var sum = CommonParallel.Aggregate( - startIndex, - endIndex, - index => _nonZeroValues[index] * columnVector[_columnIndices[index]]); - resultSparseMatrix.SetValueAt(row, column, sum); - } + // Multiply row of matrix A on column of matrix B + otherSparseMatrix.Column(column, columnVector); + var sum = CommonParallel.Aggregate( + startIndex, + endIndex, + index => _nonZeroValues[index] * columnVector[_columnIndices[index]]); + resultSparseMatrix.SetValueAt(row, column, sum); } } } @@ -1253,6 +1315,98 @@ namespace MathNet.Numerics.LinearAlgebra.Double return result; } + /// + /// Multiplies this dense matrix with transpose of another dense matrix and places the results into the result dense matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If the other matrix is . + /// If the result matrix is . + /// If this.Columns != other.Rows. + /// If the result matrix's dimensions are not the this.Rows x other.Columns. + public override void TransposeAndMultiply(Matrix other, Matrix result) + { + var otherSparse = other as SparseMatrix; + var resultSparse = result as SparseMatrix; + + if (otherSparse == null || resultSparse == null) + { + base.TransposeAndMultiply(other, result); + return; + } + + if (ColumnCount != otherSparse.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if ((resultSparse.RowCount != RowCount) || (resultSparse.ColumnCount != otherSparse.RowCount)) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + resultSparse.Clear(); + for (var j = 0; j < RowCount; j++) + { + // Get the begin / end index for the row + var startIndexOther = otherSparse._rowIndex[j]; + var endIndexOther = j < otherSparse._rowIndex.Length - 1 ? otherSparse._rowIndex[j + 1] : otherSparse.NonZerosCount; + if (startIndexOther == endIndexOther) + { + continue; + } + + for (var i = 0; i < RowCount; i++) + { + // Multiply row of matrix A on row of matrix B + // Get the begin / end index for the row + var startIndexThis = _rowIndex[i]; + var endIndexThis = i < _rowIndex.Length - 1 ? _rowIndex[i + 1] : NonZerosCount; + if (startIndexThis == endIndexThis) + { + continue; + } + + var i1 = i; + var sum = CommonParallel.Aggregate( + startIndexOther, + endIndexOther, + index => + { + var ind = FindItem(i1, otherSparse._columnIndices[index]); + return ind >= 0 ? otherSparse._nonZeroValues[index] * _nonZeroValues[ind] : 0.0; + }); + + resultSparse.SetValueAt(i, j, sum + result.At(i, j)); + } + } + } + + /// + /// Multiplies this matrix with transpose of another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If this.Columns != other.Rows. + /// If the other matrix is . + /// The result of multiplication. + public override Matrix TransposeAndMultiply(Matrix other) + { + var otherSparse = other as SparseMatrix; + if (otherSparse == null) + { + return base.TransposeAndMultiply(other); + } + + if (ColumnCount != otherSparse.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var result = (SparseMatrix)CreateMatrix(RowCount, other.RowCount); + TransposeAndMultiply(other, result); + return result; + } + /// /// Multiplies two sparse matrices. /// diff --git a/src/Numerics/LinearAlgebra/Double/SparseVector.cs b/src/Numerics/LinearAlgebra/Double/SparseVector.cs index 228929be..b1a24145 100644 --- a/src/Numerics/LinearAlgebra/Double/SparseVector.cs +++ b/src/Numerics/LinearAlgebra/Double/SparseVector.cs @@ -1230,19 +1230,23 @@ namespace MathNet.Numerics.LinearAlgebra.Double { throw new ArgumentOutOfRangeException("p"); } - else if (Double.IsPositiveInfinity(p)) + + if (NonZerosCount == 0) { - return CommonParallel.Select(0, NonZerosCount, (index, localData) => localData = Math.Max(localData, Math.Abs(_nonZeroValues[index])), Math.Max); + return 0.0; } - else - { - var sum = CommonParallel.Aggregate( - 0, - NonZerosCount, - index => Math.Pow(Math.Abs(_nonZeroValues[index]), p)); - return Math.Pow(sum, 1.0 / p); + if (Double.IsPositiveInfinity(p)) + { + return CommonParallel.Select(0, NonZerosCount, (index, localData) => localData = Math.Max(localData, Math.Abs(_nonZeroValues[index])), Math.Max); } + + var sum = CommonParallel.Aggregate( + 0, + NonZerosCount, + index => Math.Pow(Math.Abs(_nonZeroValues[index]), p)); + + return Math.Pow(sum, 1.0 / p); } #endregion diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 8f71b135..78f3b5d1 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -104,26 +104,74 @@ + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + @@ -168,17 +216,6 @@ - - - - - - - - - - - diff --git a/src/Numerics/Precision.cs b/src/Numerics/Precision.cs index 03756181..7d9e2c3f 100644 --- a/src/Numerics/Precision.cs +++ b/src/Numerics/Precision.cs @@ -756,6 +756,28 @@ namespace MathNet.Numerics return AlmostEqualWithError(a, b, diff, _defaultSingleRelativeAccuracy); } + /// + /// Checks whether two Compex numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqual(this Complex a, Complex b) + { + // TODO - I think that it should be changed to "if (a.IsNaN() && b.IsNaN()) { return true; }" + if (a.IsNaN() || b.IsNaN()) + { + return false; + } + + if (a.IsInfinity() && b.IsInfinity()) + { + return true; + } + + return a.Real.AlmostEqual(b.Real) && a.Imaginary.AlmostEqual(b.Imaginary); + } + /// /// Checks whether two structures with precision support are almost equal. /// diff --git a/src/Numerics/Properties/Resources.Designer.cs b/src/Numerics/Properties/Resources.Designer.cs index 9795d673..4907d9a0 100644 --- a/src/Numerics/Properties/Resources.Designer.cs +++ b/src/Numerics/Properties/Resources.Designer.cs @@ -672,6 +672,24 @@ namespace MathNet.Numerics.Properties { } } + /// + /// Looks up a localized string similar to The given stop criterium already exist in the collection.. + /// + internal static string StopCriteriumDuplicate { + get { + return ResourceManager.GetString("StopCriteriumDuplicate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is no stop criterium in the collection.. + /// + internal static string StopCriteriumMissing { + get { + return ResourceManager.GetString("StopCriteriumMissing", resourceCulture); + } + } + /// /// Looks up a localized string similar to String parameter cannot be empty or null.. /// diff --git a/src/Numerics/Properties/Resources.resx b/src/Numerics/Properties/Resources.resx index 620943f2..f231e0e1 100644 --- a/src/Numerics/Properties/Resources.resx +++ b/src/Numerics/Properties/Resources.resx @@ -333,4 +333,10 @@ {0} is not a supported type. + + The given stop criterium already exist in the collection. + + + There is no stop criterium in the collection. + \ No newline at end of file diff --git a/src/Numerics/Threading/CommonParallel.cs b/src/Numerics/Threading/CommonParallel.cs index 0984ebb3..e7f89909 100644 --- a/src/Numerics/Threading/CommonParallel.cs +++ b/src/Numerics/Threading/CommonParallel.cs @@ -31,6 +31,7 @@ namespace MathNet.Numerics.Threading { using System; + using System.Numerics; #if !SILVERLIGHT using System.Collections.Concurrent; @@ -139,6 +140,67 @@ namespace MathNet.Numerics.Threading return sum; } + /// + /// Aggregates a function over a loop for Complex data type. + /// + /// Starting index of the loop. + /// Ending index of the loop + /// The function to aggregate. + /// The sum of the function over the loop. + public static Complex Aggregate(int fromInclusive, int toExclusive, Func body) + { + var sync = new object(); + var sum = Complex.Zero; + +#if SILVERLIGHT + Parallel.For( + fromInclusive, + toExclusive, + () => Complex.Zero, + (i, localData) => localData += body(i), + localResult => + { + lock (sync) + { + sum += localResult; + } + }); +#else + + if (Control.DisableParallelization || Control.NumberOfParallelWorkerThreads < 2) + { + for (var index = fromInclusive; index < toExclusive; index++) + { + sum += body(index); + } + } + else + { + Parallel.ForEach( + Partitioner.Create(fromInclusive, toExclusive), + new ParallelOptions { MaxDegreeOfParallelism = Control.NumberOfParallelWorkerThreads }, + () => Complex.Zero, + (range, loopState, localData) => + { + for (var i = range.Item1; i < range.Item2; i++) + { + localData += body(i); + } + + return localData; + }, + localResult => + { + lock (sync) + { + sum += localResult; + } + }); + } +#endif + return sum; + } + /// /// Executes each of the provided actions inside a discrete, asynchronous task. /// diff --git a/src/Silverlight/Silverlight.csproj b/src/Silverlight/Silverlight.csproj index 7a5771eb..d66ac0c6 100644 --- a/src/Silverlight/Silverlight.csproj +++ b/src/Silverlight/Silverlight.csproj @@ -221,6 +221,9 @@ LinearAlgebra\Double\DenseVector.cs + + LinearAlgebra\Double\DiagonalMatrix.cs + LinearAlgebra\Double\Factorization\Cholesky.cs @@ -239,12 +242,27 @@ LinearAlgebra\Double\Factorization\ExtensionMethods.cs + + LinearAlgebra\Double\Factorization\GramSchmidt.cs + LinearAlgebra\Double\Factorization\LU.cs LinearAlgebra\Double\Factorization\QR.cs + + LinearAlgebra\Double\Factorization\SparseCholesky.cs + + + LinearAlgebra\Double\Factorization\SparseLU.cs + + + LinearAlgebra\Double\Factorization\SparseQR.cs + + + LinearAlgebra\Double\Factorization\SparseSvd.cs + LinearAlgebra\Double\Factorization\Svd.cs @@ -281,6 +299,93 @@ LinearAlgebra\Double\Matrix.cs + + LinearAlgebra\Double\Solvers\IIterativeSolver.cs + + + LinearAlgebra\Double\Solvers\IIterativeSolverSetup.cs + + + LinearAlgebra\Double\Solvers\IIterator.cs + + + LinearAlgebra\Double\Solvers\Iterative\BiCgStab.cs + + + LinearAlgebra\Double\Solvers\Iterative\CompositeSolver.cs + + + LinearAlgebra\Double\Solvers\Iterative\GpBiCg.cs + + + LinearAlgebra\Double\Solvers\Iterative\MlkBiCgStab.cs + + + LinearAlgebra\Double\Solvers\Iterative\TFQMR.cs + + + LinearAlgebra\Double\Solvers\Iterator.cs + + + LinearAlgebra\Double\Solvers\Preconditioners\Diagonal.cs + + + LinearAlgebra\Double\Solvers\Preconditioners\Ilutp.cs + + + LinearAlgebra\Double\Solvers\Preconditioners\IlutpElementSorter.cs + + + LinearAlgebra\Double\Solvers\Preconditioners\IncompleteLU.cs + + + LinearAlgebra\Double\Solvers\Preconditioners\IPreConditioner.cs + + + LinearAlgebra\Double\Solvers\Preconditioners\UnitPreconditioner.cs + + + LinearAlgebra\Double\Solvers\Status\CalculationCancelled.cs + + + LinearAlgebra\Double\Solvers\Status\CalculationConverged.cs + + + LinearAlgebra\Double\Solvers\Status\CalculationDiverged.cs + + + LinearAlgebra\Double\Solvers\Status\CalculationFailure.cs + + + LinearAlgebra\Double\Solvers\Status\CalculationIndetermined.cs + + + LinearAlgebra\Double\Solvers\Status\CalculationRunning.cs + + + LinearAlgebra\Double\Solvers\Status\CalculationStoppedWithoutConvergence.cs + + + LinearAlgebra\Double\Solvers\Status\ICalculationStatus.cs + + + LinearAlgebra\Double\Solvers\StopCriterium\DivergenceStopCriterium.cs + + + LinearAlgebra\Double\Solvers\StopCriterium\FailureStopCriterium.cs + + + LinearAlgebra\Double\Solvers\StopCriterium\IIterationStopCriterium.cs + + + LinearAlgebra\Double\Solvers\StopCriterium\IterationCountStopCriterium.cs + + + LinearAlgebra\Double\Solvers\StopCriterium\ResidualStopCriterium.cs + + + LinearAlgebra\Double\Solvers\StopCriterium\StopLevel.cs + LinearAlgebra\Double\SparseMatrix.cs diff --git a/src/UnitTests/AssertHelpers.cs b/src/UnitTests/AssertHelpers.cs index 20c9a2b7..ffd22bb2 100644 --- a/src/UnitTests/AssertHelpers.cs +++ b/src/UnitTests/AssertHelpers.cs @@ -28,7 +28,6 @@ // OTHER DEALINGS IN THE SOFTWARE. // - namespace MathNet.Numerics.UnitTests { using System.Collections.Generic; @@ -38,8 +37,38 @@ namespace MathNet.Numerics.UnitTests /// /// A class which includes some assertion helper methods particularly for numerical code. /// - class AssertHelpers + internal class AssertHelpers { + /// + /// Asserts that the expected value and the actual value are equal. + /// + /// The expected value. + /// The actual value. + public static void AreEqual(Complex expected, Complex actual) + { + if (expected.IsNaN() && actual.IsNaN()) + { + return; + } + + if (expected.IsInfinity() && expected.IsInfinity()) + { + return; + } + + bool pass = expected.Real.AlmostEqual(actual.Real); + if (!pass) + { + Assert.Fail("Real components are not equal. Expected:{0}; Actual:{1}", expected.Real, actual.Real); + } + + pass = expected.Imaginary.AlmostEqual(actual.Imaginary); + if (!pass) + { + Assert.Fail("Imaginary components are not equal. Expected:{0}; Actual:{1}", expected.Imaginary, actual.Imaginary); + } + } + /// /// Asserts that the expected value and the actual value are equal up to a certain number of decimal places. If both /// and are NaN then no assert is thrown. @@ -49,7 +78,7 @@ namespace MathNet.Numerics.UnitTests /// The number of decimal places to agree on. public static void AlmostEqual(double expected, double actual, int decimalPlaces) { - if(double.IsNaN(expected) && double.IsNaN(actual)) + if (double.IsNaN(expected) && double.IsNaN(actual)) { return; } @@ -57,7 +86,7 @@ namespace MathNet.Numerics.UnitTests bool pass = Precision.AlmostEqualInDecimalPlaces(expected, actual, decimalPlaces); if (!pass) { - //signals Gallio that the test failed. + // signals Gallio that the test failed. Assert.Fail("Not equal within {0} places. Expected:{1}; Actual:{2}", decimalPlaces, expected, actual); } } @@ -83,7 +112,6 @@ namespace MathNet.Numerics.UnitTests } } - /// /// Asserts that the expected value and the actual value are equal up to a certain number of decimal places. /// diff --git a/src/UnitTests/LinearAlgebraTests/Double/DenseMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Double/DenseMatrixTests.cs index 2226c3fe..878b4704 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/DenseMatrixTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/DenseMatrixTests.cs @@ -59,16 +59,18 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanCreateMatrixFrom1DArray() { - Dictionary testData = new Dictionary(); - testData.Add("Singular3x3", new DenseMatrix(3, 3, new double[] { 1, 1, 1, 1, 1, 1, 2, 2, 2 })); - testData.Add("Square3x3", new DenseMatrix(3, 3, new double[] { -1.1, 0.0, -4.4, -2.2, 1.1, 5.5, -3.3, 2.2, 6.6 })); - testData.Add("Square4x4", new DenseMatrix(4, 4, new double[] { -1.1, 0.0, 1.0, -4.4, -2.2, 1.1, 2.1, 5.5, -3.3, 2.2, 6.2, 6.6, -4.4, 3.3, 4.3, -7.7 })); - testData.Add("Tall3x2", new DenseMatrix(3, 2, new double[] { -1.1, 0.0, -4.4, -2.2, 1.1, 5.5 })); - testData.Add("Wide2x3", new DenseMatrix(2, 3, new double[] { -1.1, 0.0, -2.2, 1.1, -3.3, 2.2 })); + Dictionary testData = new Dictionary + { + { "Singular3x3", new DenseMatrix(3, 3, new[] { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0 }) }, + { "Square3x3", new DenseMatrix(3, 3, new[] { -1.1, 0.0, -4.4, -2.2, 1.1, 5.5, -3.3, 2.2, 6.6 }) }, + { "Square4x4", new DenseMatrix(4, 4, new[] { -1.1, 0.0, 1.0, -4.4, -2.2, 1.1, 2.1, 5.5, -3.3, 2.2, 6.2, 6.6, -4.4, 3.3, 4.3, -7.7 }) }, + { "Tall3x2", new DenseMatrix(3, 2, new[] { -1.1, 0.0, -4.4, -2.2, 1.1, 5.5 }) }, + { "Wide2x3", new DenseMatrix(2, 3, new[] { -1.1, 0.0, -2.2, 1.1, -3.3, 2.2 }) } + }; foreach (var name in testData.Keys) { - Assert.AreEqual(testMatrices[name], testData[name]); + Assert.AreEqual(TestMatrices[name], testData[name]); } } @@ -84,9 +86,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void MatrixFrom2DArrayIsCopy() { - var matrix = new DenseMatrix(testData2D["Singular3x3"]); + var matrix = new DenseMatrix(TestData2D["Singular3x3"]); matrix[0, 0] = 10.0; - Assert.AreEqual(1.0, testData2D["Singular3x3"][0, 0]); + Assert.AreEqual(1.0, TestData2D["Singular3x3"][0, 0]); } [Test] @@ -98,12 +100,12 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Wide2x3")] public void CanCreateMatrixFrom2DArray(string name) { - var matrix = new DenseMatrix(testData2D[name]); - for (var i = 0; i < testData2D[name].GetLength(0); i++) + var matrix = new DenseMatrix(TestData2D[name]); + for (var i = 0; i < TestData2D[name].GetLength(0); i++) { - for (var j = 0; j < testData2D[name].GetLength(1); j++) + for (var j = 0; j < TestData2D[name].GetLength(1); j++) { - Assert.AreEqual(testData2D[name][i, j], matrix[i, j]); + Assert.AreEqual(TestData2D[name][i, j], matrix[i, j]); } } } @@ -147,7 +149,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void IdentityFailsWithZeroOrNegativeOrder(int order) { - var matrix = DenseMatrix.Identity(order); + DenseMatrix.Identity(order); } } } diff --git a/src/UnitTests/LinearAlgebraTests/Double/DiagonalMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Double/DiagonalMatrixTests.cs new file mode 100644 index 00000000..6a61a8b9 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/DiagonalMatrixTests.cs @@ -0,0 +1,449 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double +{ + using System; + using System.Collections.Generic; + using System.Linq; + using MbUnit.Framework; + using LinearAlgebra.Double; + + public class DiagonalMatrixTests : MatrixTests + { + [SetUp] + public override void SetupMatrices() + { + TestData2D = new Dictionary + { + { "Singular3x3", new [,] { { 1.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 3.0 } } }, + { "Square3x3", new [,] { { -1.1, 0.0, 0.0 }, { 0.0, 1.1, 0.0 }, { 0.0, 0.0, 6.6 } } }, + { "Square4x4", new [,] { { -1.1, 0.0, 0.0, 0.0 }, { 0.0, 1.1, 0.0, 0.0 }, { 0.0, 0.0, 6.2, 0.0}, { 0.0, 0.0, 0.0, -7.7 } } }, + { "Singular4x4", new [,] { { -1.1, 0.0, 0.0, 0.0 }, { 0.0, -2.2, 0.0, 0.0 }, { 0.0, 0.0, 0.0, 0.0}, { 0.0, 0.0, 0.0, -4.4 } } }, + { "Tall3x2", new [,] { { -1.1, 0.0 }, { 0.0, 1.1 }, { 0.0, 0.0 } } }, + { "Wide2x3", new [,] { { -1.1, 0.0, 0.0 }, { 0.0, 1.1, 0.0 } } } + }; + + TestMatrices = new Dictionary(); + foreach (var name in TestData2D.Keys) + { + TestMatrices.Add(name, CreateMatrix(TestData2D[name])); + } + } + + protected override Matrix CreateMatrix(int rows, int columns) + { + return new DiagonalMatrix(rows, columns); + } + + protected override Matrix CreateMatrix(double[,] data) + { + return new DiagonalMatrix(data); + } + + protected override Vector CreateVector(int size) + { + return new DenseVector(size); + } + + protected override Vector CreateVector(double[] data) + { + return new DenseVector(data); + } + + [Test] + public void CanCreateMatrixFromDiagonalArray() + { + var testData = new Dictionary + { + { "Singular3x3", new DiagonalMatrix(3, 3, new[] { 1.0, 0.0, 3.0}) }, + { "Square3x3", new DiagonalMatrix(4, 4, new[] { -1.1, 1.1, 6.6 }) }, + { "Square4x4", new DiagonalMatrix(4, 4, new[] { -1.1, 1.1, 6.2, -7.7 }) }, + { "Tall3x2", new DiagonalMatrix(3, 2, new[] { -1.1, 1.1 }) }, + { "Wide2x3", new DiagonalMatrix(2, 3, new[] { -1.1, 1.1 }) }, + }; + + foreach (var name in testData.Keys) + { + Assert.AreEqual(TestMatrices[name], testData[name]); + } + } + + [Test] + public void MatrixFrom1DArrayIsReference() + { + var data = new double[] { 1, 2, 3, 4, 5}; + var matrix = new DiagonalMatrix(5, 5, data); + matrix[0, 0] = 10.0; + Assert.AreEqual(10.0, data[0]); + } + + [Test] + [Row("Singular3x3")] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + public void CanCreateMatrixFrom2DArray(string name) + { + var matrix = new DiagonalMatrix(TestData2D[name]); + for (var i = 0; i < TestData2D[name].GetLength(0); i++) + { + for (var j = 0; j < TestData2D[name].GetLength(1); j++) + { + Assert.AreEqual(TestData2D[name][i, j], matrix[i, j]); + } + } + } + + [Test] + public void CanCreateMatrixWithUniformValues() + { + var matrix = new DiagonalMatrix(10, 10, 10.0); + for (var i = 0; i < matrix.RowCount; i++) + { + Assert.AreEqual(matrix[i, i], 10.0); + } + } + + [Test] + public void CanCreateIdentity() + { + var matrix = DiagonalMatrix.Identity(5); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreEqual(i == j ? 1.0 : 0.0, matrix[i, j]); + } + } + } + + [Test] + [Row(0)] + [Row(-1)] + [ExpectedArgumentException] + public void IdentityFailsWithZeroOrNegativeOrder(int order) + { + DiagonalMatrix.Identity(order); + } + + [Test] + public override void CanDiagonallyStackMatricesWithPassingResult() + { + var top = TestMatrices["Tall3x2"]; + var bottom = TestMatrices["Wide2x3"]; + var result = new SparseMatrix(top.RowCount + bottom.RowCount, top.ColumnCount + bottom.ColumnCount); + top.DiagonalStack(bottom, result); + Assert.AreEqual(top.RowCount + bottom.RowCount, result.RowCount); + Assert.AreEqual(top.ColumnCount + bottom.ColumnCount, result.ColumnCount); + + for (var i = 0; i < result.RowCount; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + if (i < top.RowCount && j < top.ColumnCount) + { + Assert.AreEqual(top[i, j], result[i, j]); + } + else if (i >= top.RowCount && j >= top.ColumnCount) + { + Assert.AreEqual(bottom[i - top.RowCount, j - top.ColumnCount], result[i, j]); + } + else + { + Assert.AreEqual(0, result[i, j]); + } + } + } + } + + public override void CanMultiplyMatrixWithMatrixIntoResult(string nameA, string nameB) + { + var matrixA = TestMatrices[nameA]; + var matrixB = TestMatrices[nameB]; + var matrixC = new SparseMatrix(matrixA.RowCount, matrixB.ColumnCount); + matrixA.Multiply(matrixB, matrixC); + + Assert.AreEqual(matrixC.RowCount, matrixA.RowCount); + Assert.AreEqual(matrixC.ColumnCount, matrixB.ColumnCount); + + for (var i = 0; i < matrixC.RowCount; i++) + { + for (var j = 0; j < matrixC.ColumnCount; j++) + { + AssertHelpers.AlmostEqual(matrixA.Row(i) * matrixB.Column(j), matrixC[i, j], 15); + } + } + } + + [Test] + [Row("Singular3x3")] + [ExpectedException(typeof(InvalidOperationException))] + public void CanPermuteMatrixRowsThrowException(string name) + { + var matrix = CreateMatrix(TestData2D[name]); + var matrixp = CreateMatrix(TestData2D[name]); + + var permutation = new Permutation(new[] { 2, 0, 1 }); + matrixp.PermuteRows(permutation); + + Assert.AreNotSame(matrix, matrixp); + Assert.AreEqual(matrix.RowCount, matrixp.RowCount); + Assert.AreEqual(matrix.ColumnCount, matrixp.ColumnCount); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreEqual(matrix[i, j], matrixp[permutation[i], j]); + } + } + } + + [Test] + [Row("Singular3x3")] + [ExpectedException(typeof(InvalidOperationException))] + public void CanPermuteMatrixColumnsThrowException(string name) + { + var matrix = CreateMatrix(TestData2D[name]); + var matrixp = CreateMatrix(TestData2D[name]); + + var permutation = new Permutation(new[] { 2, 0, 1 }); + matrixp.PermuteColumns(permutation); + + Assert.AreNotSame(matrix, matrixp); + Assert.AreEqual(matrix.RowCount, matrixp.RowCount); + Assert.AreEqual(matrix.ColumnCount, matrixp.ColumnCount); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreEqual(matrix[i, j], matrixp[i, permutation[j]]); + } + } + } + + public override void CanPermuteMatrixRows(string name) + { + } + + public override void CanPermuteMatrixColumns(string name) + { + } + + public override void PointwiseDivideResult() + { + foreach (var data in TestMatrices.Values) + { + var other = data.Clone(); + var result = data.Clone(); + data.PointwiseDivide(other, result); + var min = Math.Min(data.RowCount, data.ColumnCount); + for (var i = 0; i < min; i++) + { + Assert.AreEqual(data[i, i] / other[i, i], result[i, i]); + } + + result = data.PointwiseDivide(other); + for (var i = 0; i < min; i++) + { + Assert.AreEqual(data[i, i] / other[i, i], result[i, i]); + } + } + } + + public override void SetColumnWithArray(string name, double[] column) + { + try + { + // Pass all invoke to base + base.SetColumnWithArray(name, column); + } + catch(AggregateException ex) + { + // Supress only IndexOutOfRangeException exceptions due to Diagonal matrix nature + if (ex.InnerExceptions.Any(innerException => !(innerException is IndexOutOfRangeException))) + { + throw; + } + } + } + + public override void SetColumnWithVector(string name, double[] column) + { + try + { + // Pass all invoke to base + base.SetColumnWithVector(name, column); + } + catch (AggregateException ex) + { + // Supress only IndexOutOfRangeException exceptions due to Diagonal matrix nature + if (ex.InnerExceptions.Any(innerException => !(innerException is IndexOutOfRangeException))) + { + throw; + } + } + } + + public override void SetRowWithArray(string name, double[] row) + { + try + { + // Pass all invoke to base + base.SetRowWithArray(name, row); + } + catch (AggregateException ex) + { + // Supress only IndexOutOfRangeException exceptions due to Diagonal matrix nature + if (ex.InnerExceptions.Any(innerException => !(innerException is IndexOutOfRangeException))) + { + throw; + } + } + } + + public override void SetRowWithVector(string name, double[] row) + { + try + { + // Pass all invoke to base + base.SetRowWithVector(name, row); + } + catch (AggregateException ex) + { + // Supress only IndexOutOfRangeException exceptions due to Diagonal matrix nature + if (ex.InnerExceptions.Any(innerException => !(innerException is IndexOutOfRangeException))) + { + throw; + } + } + } + + public override void SetSubMatrix(int rowStart, int rowLength, int colStart, int colLength) + { + try + { + // Pass all invoke to base + base.SetSubMatrix(rowStart, rowLength, colStart, colLength); + } + catch (AggregateException ex) + { + // Supress only IndexOutOfRangeException exceptions due to Diagonal matrix nature + if (ex.InnerExceptions.Any(innerException => !(innerException is IndexOutOfRangeException))) + { + throw; + } + } + } + + public override void FrobeniusNorm() + { + var matrix = TestMatrices["Square3x3"]; + var denseMatrix = new DenseMatrix(TestData2D["Square3x3"]); + AssertHelpers.AlmostEqual(denseMatrix.FrobeniusNorm(), matrix.FrobeniusNorm(), 14); + + matrix = TestMatrices["Wide2x3"]; + denseMatrix = new DenseMatrix(TestData2D["Wide2x3"]); + AssertHelpers.AlmostEqual(denseMatrix.FrobeniusNorm(), matrix.FrobeniusNorm(), 14); + + matrix = TestMatrices["Tall3x2"]; + denseMatrix = new DenseMatrix(TestData2D["Tall3x2"]); + AssertHelpers.AlmostEqual(denseMatrix.FrobeniusNorm(), matrix.FrobeniusNorm(), 14); + } + + public override void InfinityNorm() + { + var matrix = TestMatrices["Square3x3"]; + var denseMatrix = new DenseMatrix(TestData2D["Square3x3"]); + AssertHelpers.AlmostEqual(denseMatrix.InfinityNorm(), matrix.InfinityNorm(), 14); + + matrix = TestMatrices["Wide2x3"]; + denseMatrix = new DenseMatrix(TestData2D["Wide2x3"]); + AssertHelpers.AlmostEqual(denseMatrix.InfinityNorm(), matrix.InfinityNorm(), 14); + + matrix = TestMatrices["Tall3x2"]; + denseMatrix = new DenseMatrix(TestData2D["Tall3x2"]); + AssertHelpers.AlmostEqual(denseMatrix.InfinityNorm(), matrix.InfinityNorm(), 14); + } + + public override void L1Norm() + { + var matrix = TestMatrices["Square3x3"]; + var denseMatrix = new DenseMatrix(TestData2D["Square3x3"]); + AssertHelpers.AlmostEqual(denseMatrix.L1Norm(), matrix.L1Norm(), 14); + + matrix = TestMatrices["Wide2x3"]; + denseMatrix = new DenseMatrix(TestData2D["Wide2x3"]); + AssertHelpers.AlmostEqual(denseMatrix.L1Norm(), matrix.L1Norm(), 14); + + matrix = TestMatrices["Tall3x2"]; + denseMatrix = new DenseMatrix(TestData2D["Tall3x2"]); + AssertHelpers.AlmostEqual(denseMatrix.L1Norm(), matrix.L1Norm(), 14); + } + + public override void L2Norm() + { + var matrix = TestMatrices["Square3x3"]; + var denseMatrix = new DenseMatrix(TestData2D["Square3x3"]); + AssertHelpers.AlmostEqual(denseMatrix.L2Norm(), matrix.L2Norm(), 14); + + matrix = TestMatrices["Wide2x3"]; + denseMatrix = new DenseMatrix(TestData2D["Wide2x3"]); + AssertHelpers.AlmostEqual(denseMatrix.L2Norm(), matrix.L2Norm(), 14); + + matrix = TestMatrices["Tall3x2"]; + denseMatrix = new DenseMatrix(TestData2D["Tall3x2"]); + AssertHelpers.AlmostEqual(denseMatrix.L2Norm(), matrix.L2Norm(), 14); + } + + [Test] + [MultipleAsserts] + public void Determinant() + { + var matrix = TestMatrices["Square3x3"]; + var denseMatrix = new DenseMatrix(TestData2D["Square3x3"]); + AssertHelpers.AlmostEqual(denseMatrix.Determinant(), matrix.Determinant(), 14); + + matrix = TestMatrices["Square4x4"]; + denseMatrix = new DenseMatrix(TestData2D["Square4x4"]); + AssertHelpers.AlmostEqual(denseMatrix.Determinant(), matrix.Determinant(), 14); + } + + [Test] + [ExpectedArgumentException] + public void DeterminantNotSquareMatrixThrowException() + { + var matrix = TestMatrices["Tall3x2"]; + matrix.Determinant(); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs new file mode 100644 index 00000000..0ecbe6c9 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs @@ -0,0 +1,330 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// 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. +// + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization +{ + using MbUnit.Framework; + using LinearAlgebra.Double.Factorization; + + public class GramSchmidtTests + { + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new GramSchmidt(null); + } + + [Test] + [ExpectedArgumentException] + public void WideMatrixThrowsInvalidMatrixOperationException() + { + new GramSchmidt(new UserDefinedMatrix(3, 4)); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorGramSchmidt = I.GramSchmidt(); + + Assert.AreEqual(I.RowCount, factorGramSchmidt.Q.RowCount); + Assert.AreEqual(I.ColumnCount, factorGramSchmidt.Q.ColumnCount); + + for (var i = 0; i < factorGramSchmidt.R.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.R.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(1.0, factorGramSchmidt.R[i, j]); + } + else + { + Assert.AreEqual(0.0, factorGramSchmidt.R[i, j]); + } + } + } + + for (var i = 0; i < factorGramSchmidt.Q.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.Q.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(1.0, factorGramSchmidt.Q[i, j]); + } + else + { + Assert.AreEqual(0.0, factorGramSchmidt.Q[i, j]); + } + } + } + } + + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorGramSchmidt = I.GramSchmidt(); + Assert.AreEqual(1.0, factorGramSchmidt.Determinant); + } + + [Test] + [Row(1,1)] + [Row(2,2)] + [Row(5,5)] + [Row(10,6)] + [Row(50,48)] + [Row(100,98)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var factorGramSchmidt = matrixA.GramSchmidt(); + + // Make sure the Q has the right dimensions. + Assert.AreEqual(row, factorGramSchmidt.Q.RowCount); + Assert.AreEqual(column, factorGramSchmidt.Q.ColumnCount); + + // Make sure the R has the right dimensions. + Assert.AreEqual(column, factorGramSchmidt.R.RowCount); + Assert.AreEqual(column, factorGramSchmidt.R.ColumnCount); + + // Make sure the R factor is upper triangular. + for (var i = 0; i < factorGramSchmidt.R.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.R.ColumnCount; j++) + { + if (i > j) + { + Assert.AreEqual(0.0, factorGramSchmidt.R[i, j]); + } + } + } + + // Make sure the Q*R is the original matrix. + var matrixQfromR = factorGramSchmidt.Q * factorGramSchmidt.R; + for (var i = 0; i < matrixQfromR.RowCount; i++) + { + for (var j = 0; j < matrixQfromR.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixA[i, j], matrixQfromR[i, j], 1.0e-11); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorGramSchmidt.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < order; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-11); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorGramSchmidt.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-11); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVectorWhenResultVectorGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorGramSchmidt.Solve(vectorb,resultx); + + Assert.AreEqual(vectorb.Count, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-11); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure b didn't change. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreEqual(vectorbCopy[i], vectorb[i]); + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixWhenResultMatrixGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorGramSchmidt.Solve(matrixB,matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-11); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure B didn't change. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreEqual(matrixBCopy[i, j], matrixB[i, j]); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/LinearAlgebraProviderTests.cs b/src/UnitTests/LinearAlgebraTests/Double/LinearAlgebraProviderTests.cs index c89c5ed0..cd6ac547 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/LinearAlgebraProviderTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/LinearAlgebraProviderTests.cs @@ -147,8 +147,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Tall3x2", "Wide2x3")] public void CanMatrixMultiply(string nameX, string nameY) { - var x = (DenseMatrix)testMatrices[nameX]; - var y = (DenseMatrix)testMatrices[nameY]; + var x = (DenseMatrix)TestMatrices[nameX]; + var y = (DenseMatrix)TestMatrices[nameY]; var c = (DenseMatrix)CreateMatrix(x.RowCount, y.ColumnCount); Provider.MatrixMultiply(x.Data, x.RowCount, x.ColumnCount, y.Data, y.RowCount, y.ColumnCount, c.Data); @@ -170,8 +170,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Tall3x2", "Wide2x3")] public void CanMatrixMultiplyWithUpdate(string nameX, string nameY) { - var x = (DenseMatrix)testMatrices[nameX]; - var y = (DenseMatrix)testMatrices[nameY]; + var x = (DenseMatrix)TestMatrices[nameX]; + var y = (DenseMatrix)TestMatrices[nameY]; var c = (DenseMatrix)CreateMatrix(x.RowCount, y.ColumnCount); Provider.MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.DontTranspose, 2.0, x.Data, x.RowCount, x.ColumnCount, y.Data, y.RowCount, y.ColumnCount, 1.0, c.Data); diff --git a/src/UnitTests/LinearAlgebraTests/Double/MatrixLoader.cs b/src/UnitTests/LinearAlgebraTests/Double/MatrixLoader.cs index 776bf053..533836cd 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/MatrixLoader.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/MatrixLoader.cs @@ -37,8 +37,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double public abstract class MatrixLoader { - protected Dictionary testData2D; - protected Dictionary testMatrices; + protected Dictionary TestData2D; + protected Dictionary TestMatrices; protected abstract Matrix CreateMatrix(int rows, int columns); protected abstract Matrix CreateMatrix(double[,] data); @@ -46,20 +46,22 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double protected abstract Vector CreateVector(double[] data); [SetUp] - public void SetupMatrices() + public virtual void SetupMatrices() { - testData2D = new Dictionary(); - testData2D.Add("Singular3x3", new double[,] { { 1, 1, 2 }, { 1, 1, 2 }, { 1, 1, 2 } }); - testData2D.Add("Square3x3", new double[,] { { -1.1, -2.2, -3.3 }, { 0, 1.1, 2.2 }, { -4.4, 5.5, 6.6 } }); - testData2D.Add("Square4x4", new double[,] { { -1.1, -2.2, -3.3, -4.4 }, { 0, 1.1, 2.2, 3.3 }, { 1.0, 2.1, 6.2, 4.3 }, { -4.4, 5.5, 6.6, -7.7 } }); - testData2D.Add("Singular4x4", new double[,] { { -1.1, -2.2, -3.3, -4.4 }, { -1.1, -2.2, -3.3, -4.4 }, { -1.1, -2.2, -3.3, -4.4 }, { -1.1, -2.2, -3.3, -4.4 } }); - testData2D.Add("Tall3x2", new double[,] { { -1.1, -2.2 }, { 0, 1.1 }, { -4.4, 5.5 } }); - testData2D.Add("Wide2x3", new double[,] { { -1.1, -2.2, -3.3 }, { 0, 1.1, 2.2 } }); - - testMatrices = new Dictionary(); - foreach (var name in testData2D.Keys) + TestData2D = new Dictionary + { + { "Singular3x3", new [,] { { 1.0, 1.0, 2.0 }, { 1.0, 1.0, 2.0 }, { 1.0, 1.0, 2.0 } } }, + { "Square3x3", new[,] { { -1.1, -2.2, -3.3 }, { 0.0, 1.1, 2.2 }, { -4.4, 5.5, 6.6 } } }, + { "Square4x4", new[,] { { -1.1, -2.2, -3.3, -4.4 }, { 0.0, 1.1, 2.2, 3.3 }, { 1.0, 2.1, 6.2, 4.3 }, { -4.4, 5.5, 6.6, -7.7 } } }, + { "Singular4x4", new[,] { { -1.1, -2.2, -3.3, -4.4 }, { -1.1, -2.2, -3.3, -4.4 }, { -1.1, -2.2, -3.3, -4.4 }, { -1.1, -2.2, -3.3, -4.4 } } }, + { "Tall3x2", new[,] { { -1.1, -2.2 }, { 0.0, 1.1 }, { -4.4, 5.5 } } }, + { "Wide2x3", new[,] { { -1.1, -2.2, -3.3 }, { 0.0, 1.1, 2.2 } } }, + }; + + TestMatrices = new Dictionary(); + foreach (var name in TestData2D.Keys) { - testMatrices.Add(name, CreateMatrix(testData2D[name])); + TestMatrices.Add(name, CreateMatrix(TestData2D[name])); } } @@ -68,17 +70,17 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double // Fill a matrix with standard random numbers. var normal = new Distributions.Normal(); normal.RandomSource = new Random.MersenneTwister(1); - var A = new DenseMatrix(row, col); + var matrixA = new DenseMatrix(row, col); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { - A[i, j] = normal.Sample(); + matrixA[i, j] = normal.Sample(); } } // Generate a matrix which is positive definite. - return A; + return matrixA; } public static Matrix GenerateRandomPositiveDefiniteDenseMatrix(int order) @@ -86,17 +88,17 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double // Fill a matrix with standard random numbers. var normal = new Distributions.Normal(); normal.RandomSource = new Random.MersenneTwister(1); - var A = new DenseMatrix(order); + var matrixA = new DenseMatrix(order); for (int i = 0; i < order; i++) { for (int j = 0; j < order; j++) { - A[i, j] = normal.Sample(); + matrixA[i, j] = normal.Sample(); } } // Generate a matrix which is positive definite. - return A.Transpose() * A; + return matrixA.Transpose() * matrixA; } public static Vector GenerateRandomDenseVector(int order) @@ -119,17 +121,17 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double // Fill a matrix with standard random numbers. var normal = new Distributions.Normal(); normal.RandomSource = new Random.MersenneTwister(1); - var A = new UserDefinedMatrix(row, col); + var matrixA = new UserDefinedMatrix(row, col); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { - A[i, j] = normal.Sample(); + matrixA[i, j] = normal.Sample(); } } // Generate a matrix which is positive definite. - return A; + return matrixA; } public static Matrix GenerateRandomPositiveDefiniteUserDefinedMatrix(int order) @@ -137,17 +139,17 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double // Fill a matrix with standard random numbers. var normal = new Distributions.Normal(); normal.RandomSource = new Random.MersenneTwister(1); - var A = new UserDefinedMatrix(order); + var matrixA = new UserDefinedMatrix(order); for (int i = 0; i < order; i++) { for (int j = 0; j < order; j++) { - A[i, j] = normal.Sample(); + matrixA[i, j] = normal.Sample(); } } // Generate a matrix which is positive definite. - return A.Transpose() * A; + return matrixA.Transpose() * matrixA; } public static Vector GenerateRandomUserDefinedVector(int order) diff --git a/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.Arithmetic.cs b/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.Arithmetic.cs index 5aa83c8d..a405cc3c 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.Arithmetic.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.Arithmetic.cs @@ -40,7 +40,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanMultiplyWithScalar(double scalar) { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var clone = matrix.Clone(); clone.Multiply(scalar); @@ -56,7 +56,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanMultiplyWithVector() { - var A = this.testMatrices["Singular3x3"]; + var A = this.TestMatrices["Singular3x3"]; var x = new DenseVector(new[] { 1.0, 2.0, 3.0 }); var y = A * x; @@ -73,7 +73,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanMultiplyWithVectorIntoResult() { - var A = this.testMatrices["Singular3x3"]; + var A = this.TestMatrices["Singular3x3"]; var x = new DenseVector(new[] { 1.0, 2.0, 3.0 }); var y = new DenseVector(3); A.Multiply(x, y); @@ -89,7 +89,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanMultiplyWithVectorIntoResultWhenUpdatingInputArgument() { - var A = this.testMatrices["Singular3x3"]; + var A = this.TestMatrices["Singular3x3"]; var x = new DenseVector(new[] { 1.0, 2.0, 3.0 }); var y = x; A.Multiply(x, x); @@ -109,7 +109,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void MultiplyWithVectorIntoResultFailsWhenResultIsNull() { - var A = this.testMatrices["Singular3x3"]; + var A = this.TestMatrices["Singular3x3"]; var x = new DenseVector(new[] { 1.0, 2.0, 3.0 }); Vector y = null; A.Multiply(x, y); @@ -119,7 +119,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void MultiplyWithVectorIntoResultFailsWhenResultIsTooLarge() { - var A = this.testMatrices["Singular3x3"]; + var A = this.TestMatrices["Singular3x3"]; var x = new DenseVector(new[] { 1.0, 2.0, 3.0 }); Vector y = new DenseVector(4); A.Multiply(x, y); @@ -132,7 +132,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanOperatorLeftMultiplyWithScalar(double scalar) { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var clone = matrix * scalar; for (var i = 0; i < matrix.RowCount; i++) @@ -151,7 +151,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanOperatorRightMultiplyWithScalar(double scalar) { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var clone = matrix * scalar; for (var i = 0; i < matrix.RowCount; i++) @@ -170,7 +170,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanMultiplyWithScalarIntoResult(double scalar) { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var result = matrix.Clone(); matrix.Multiply(scalar, result); @@ -187,7 +187,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void MultiplyWithScalarIntoResultFailsWhenResultIsNull() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; Matrix result = null; matrix.Multiply(2.3, result); } @@ -196,7 +196,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void MultiplyWithScalarFailsWhenResultHasMoreRows() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var result = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); matrix.Multiply(2.3, result); } @@ -205,7 +205,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void MultiplyWithScalarFailsWhenResultHasMoreColumns() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var result = this.CreateMatrix(matrix.RowCount, matrix.ColumnCount + 1); matrix.Multiply(2.3, result); } @@ -231,8 +231,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Singular4x4", "Square4x4")] public void CanAddMatrix(string mtxA, string mtxB) { - var A = this.testMatrices[mtxA]; - var B = this.testMatrices[mtxB]; + var A = this.TestMatrices[mtxA]; + var B = this.TestMatrices[mtxB]; var matrix = A.Clone(); matrix.Add(B); @@ -249,7 +249,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void AddMatrixThrowsExceptionWhenArgumentIsNull() { - var matrix = this.testMatrices["Singular4x4"]; + var matrix = this.TestMatrices["Singular4x4"]; Matrix other = null; matrix.Add(other); } @@ -258,8 +258,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void AddMatrixThrowsExceptionArgumentHasTooFewColumns() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Tall3x2"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Tall3x2"]; matrix.Add(other); } @@ -267,8 +267,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void AddMatrixThrowsExceptionArgumentHasTooFewRows() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Wide2x3"]; matrix.Add(other); } @@ -277,8 +277,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Singular4x4", "Square4x4")] public void AddOperator(string mtxA, string mtxB) { - var A = this.testMatrices[mtxA]; - var B = this.testMatrices[mtxB]; + var A = this.TestMatrices[mtxA]; + var B = this.TestMatrices[mtxB]; var result = A + B; for (var i = 0; i < A.RowCount; i++) @@ -295,7 +295,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double public void AddOperatorThrowsExceptionWhenLeftsideIsNull() { Matrix matrix = null; - var other = this.testMatrices["Singular3x3"]; + var other = this.TestMatrices["Singular3x3"]; var result = matrix + other; } @@ -303,7 +303,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void AddOperatorThrowsExceptionWhenRightsideIsNull() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; Matrix other = null; var result = matrix + other; } @@ -312,8 +312,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void AddOperatorThrowsExceptionWhenRightsideHasTooFewColumns() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Tall3x2"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Tall3x2"]; var result = matrix + other; } @@ -321,8 +321,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void AddOperatorThrowsExceptionWhenRightsideHasTooFewRows() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Wide2x3"]; var result = matrix + other; } @@ -331,8 +331,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Singular4x4", "Square4x4")] public void CanSubtractMatrix(string mtxA, string mtxB) { - var A = this.testMatrices[mtxA]; - var B = this.testMatrices[mtxB]; + var A = this.TestMatrices[mtxA]; + var B = this.TestMatrices[mtxB]; var matrix = A.Clone(); matrix.Subtract(B); @@ -349,7 +349,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void SubtractMatrixThrowsExceptionWhenRightSideIsNull() { - var matrix = this.testMatrices["Singular4x4"]; + var matrix = this.TestMatrices["Singular4x4"]; Matrix other = null; matrix.Subtract(other); } @@ -358,8 +358,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void SubtractMatrixThrowsExceptionWhenRightSideHasTooFewColumns() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Tall3x2"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Tall3x2"]; matrix.Subtract(other); } @@ -367,8 +367,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void SubtractMatrixThrowsExceptionWhenRightSideHasTooFewRows() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Wide2x3"]; matrix.Subtract(other); } @@ -377,8 +377,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Singular4x4", "Square4x4")] public void SubtractOperator(string mtxA, string mtxB) { - var A = this.testMatrices[mtxA]; - var B = this.testMatrices[mtxB]; + var A = this.TestMatrices[mtxA]; + var B = this.TestMatrices[mtxB]; var result = A - B; for (var i = 0; i < A.RowCount; i++) @@ -395,7 +395,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double public void SubtractOperatorThrowsExceptionWhenLeftsideIsNull() { Matrix matrix = null; - var other = this.testMatrices["Singular3x3"]; + var other = this.TestMatrices["Singular3x3"]; var result = matrix - other; } @@ -403,7 +403,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void SubtractOperatorThrowsExceptionWhenRightsideIsNull() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; Matrix other = null; var result = matrix - other; } @@ -412,8 +412,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void SubtractOperatorThrowsExceptionWhenRightsideHasTooFewColumns() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Tall3x2"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Tall3x2"]; var result = matrix - other; } @@ -421,8 +421,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void SubtractOperatorThrowsExceptionWhenRightsideHasTooFewRows() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Wide2x3"]; var result = matrix - other; } @@ -435,8 +435,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanMultiplyMatrixWithMatrix(string nameA, string nameB) { - var A = this.testMatrices[nameA]; - var B = this.testMatrices[nameB]; + var A = this.TestMatrices[nameA]; + var B = this.TestMatrices[nameB]; var C = A * B; Assert.AreEqual(C.RowCount, A.RowCount); @@ -451,12 +451,80 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double } } + [Test] + [Row("Singular3x3")] + [Row("Singular4x4")] + [Row("Wide2x3")] + [Row("Tall3x2")] + [MultipleAsserts] + public void CanTransposeAndMultiplyMatrixWithMatrix(string nameA) + { + var A = this.TestMatrices[nameA]; + var B = this.TestMatrices[nameA]; + var C = A.TransposeAndMultiply(B); + + Assert.AreEqual(C.RowCount, A.RowCount); + Assert.AreEqual(C.ColumnCount, B.RowCount); + + for (var i = 0; i < C.RowCount; i++) + { + for (var j = 0; j < C.ColumnCount; j++) + { + AssertHelpers.AlmostEqual(A.Row(i) * B.Row(j), C[i, j], 15); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TransposeAndMultiplyMatrixMatrixFailsWhenSizesAreIncompatible() + { + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Tall3x2"]; + var result = matrix.TransposeAndMultiply(other); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TransposeAndMultiplyMatrixMatrixFailsWhenRightArgumentIsNull() + { + var matrix = this.TestMatrices["Wide2x3"]; + Matrix other = null; + var result = matrix.TransposeAndMultiply(other); + } + + [Test] + [Row("Singular3x3")] + [Row("Singular4x4")] + [Row("Wide2x3")] + [Row("Wide2x3")] + [Row("Tall3x2")] + [MultipleAsserts] + public void CanTransposeAndMultiplyMatrixWithMatrixIntoResult(string nameA) + { + var A = this.TestMatrices[nameA]; + var B = this.TestMatrices[nameA]; + var C = this.CreateMatrix(A.RowCount, B.RowCount); + A.TransposeAndMultiply(B, C); + + Assert.AreEqual(C.RowCount, A.RowCount); + Assert.AreEqual(C.ColumnCount, B.RowCount); + + for (var i = 0; i < C.RowCount; i++) + { + for (var j = 0; j < C.ColumnCount; j++) + { + AssertHelpers.AlmostEqual(A.Row(i) * B.Row(j), C[i, j], 15); + } + } + } + [Test] [ExpectedException(typeof(ArgumentException))] public void MultiplyMatrixMatrixFailsWhenSizesAreIncompatible() { - var matrix = this.testMatrices["Singular3x3"]; - var other = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Singular3x3"]; + var other = this.TestMatrices["Wide2x3"]; var result = matrix * other; } @@ -465,7 +533,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double public void MultiplyMatrixMatrixFailsWhenLeftArgumentIsNull() { Matrix matrix = null; - var other = this.testMatrices["Wide2x3"]; + var other = this.TestMatrices["Wide2x3"]; var result = matrix * other; } @@ -473,7 +541,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void MultiplyMatrixMatrixFailsWhenRightArgumentIsNull() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; Matrix other = null; var result = matrix * other; } @@ -485,10 +553,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Wide2x3", "Tall3x2")] [Row("Tall3x2", "Wide2x3")] [MultipleAsserts] - public void CanMultiplyMatrixWithMatrixIntoResult(string nameA, string nameB) + public virtual void CanMultiplyMatrixWithMatrixIntoResult(string nameA, string nameB) { - var A = this.testMatrices[nameA]; - var B = this.testMatrices[nameB]; + var A = this.TestMatrices[nameA]; + var B = this.TestMatrices[nameB]; var C = this.CreateMatrix(A.RowCount, B.ColumnCount); A.Multiply(B, C); @@ -513,7 +581,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanNegate(string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var copy = matrix.Clone(); copy.Negate(); @@ -536,7 +604,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanNegateIntoResult(string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var copy = matrix.Clone(); matrix.Negate(copy); @@ -554,7 +622,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void NegateIntoResultFailsWhenResultIsNull() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; Matrix copy = null; matrix.Negate(copy); } @@ -563,7 +631,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void NegateIntoResultFailsWhenResultHasMoreRows() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var target = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); matrix.Negate(target); } @@ -572,7 +640,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void NegateIntoResultFailsWhenResultHasMoreColumns() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var target = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); matrix.Negate(target); } @@ -581,8 +649,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void KroneckerProduct() { - var A = this.testMatrices["Wide2x3"]; - var B = this.testMatrices["Square3x3"]; + var A = this.TestMatrices["Wide2x3"]; + var B = this.TestMatrices["Square3x3"]; var result = this.CreateMatrix(A.RowCount * B.RowCount, A.ColumnCount * B.ColumnCount); A.KroneckerProduct(B, result); for (var i = 0; i < A.RowCount; i++) @@ -603,8 +671,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void KroneckerProductResult() { - var A = this.testMatrices["Wide2x3"]; - var B = this.testMatrices["Square3x3"]; + var A = this.TestMatrices["Wide2x3"]; + var B = this.TestMatrices["Square3x3"]; var result = A.KroneckerProduct(B); for (var i = 0; i < A.RowCount; i++) { @@ -627,7 +695,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(-4, ExpectedException = typeof(ArgumentOutOfRangeException))] public void NormalizeColumns(int pValue) { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var result = matrix.NormalizeColumns(pValue); for (var j = 0; j < result.ColumnCount; j++) { @@ -642,7 +710,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(-3, ExpectedException = typeof(ArgumentOutOfRangeException))] public void NormalizeRows(int pValue) { - var matrix = this.testMatrices["Singular3x3"].NormalizeRows(pValue); + var matrix = this.TestMatrices["Singular3x3"].NormalizeRows(pValue); for (var i = 0; i < matrix.RowCount; i++) { var row = matrix.Row(i); @@ -653,7 +721,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void PointwiseMultiplyResult() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var other = data.Clone(); var result = data.Clone(); @@ -681,7 +749,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void PointwiseMultiplyWithNullOtherShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; Matrix other = null; var result = matrix.Clone(); matrix.PointwiseMultiply(other, result); @@ -691,7 +759,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void PointwiseMultiplyWithResultNullShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; var other = matrix.Clone(); matrix.PointwiseMultiply(other, null); } @@ -700,7 +768,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void PointwiseMultiplyWithInvalidOtherMatrixDimensionsShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; var other = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); var result = matrix.Clone(); matrix.PointwiseMultiply(other, result); @@ -710,16 +778,16 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void PointwiseMultiplyWithInvalidResultMatrixDimensionsShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; var other = matrix.Clone(); var result = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); matrix.PointwiseMultiply(other, result); } [Test] - public void PointwiseDivideIResult() + public virtual void PointwiseDivideResult() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var other = data.Clone(); var result = data.Clone(); @@ -747,7 +815,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void PointwiseDivideWithNullOtherShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; Matrix other = null; var result = matrix.Clone(); matrix.PointwiseDivide(other, result); @@ -757,7 +825,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void PointwiseDivideWithResultNullShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; var other = matrix.Clone(); matrix.PointwiseDivide(other, null); } @@ -766,7 +834,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void PointwiseDivideWithInvalidOtherMatrixDimensionsShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; var other = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); var result = matrix.Clone(); matrix.PointwiseDivide(other, result); @@ -776,7 +844,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void PointwiseDivideWithInvalidResultMatrixDimensionsShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; var other = matrix.Clone(); var result = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); matrix.PointwiseDivide(other, result); @@ -803,7 +871,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void Trace() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; var trace = matrix.Trace(); Assert.AreEqual(6.6, trace); } @@ -812,7 +880,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void TraceOfNonSquareMatrixShouldThrowException() { - var matrix = this.testMatrices["Wide2x3"]; + var matrix = this.TestMatrices["Wide2x3"]; var trace = matrix.Trace(); } } diff --git a/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.cs index be0eb6e2..5e025892 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/MatrixTests.cs @@ -42,7 +42,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanCloneMatrix(string name) { - var matrix = this.CreateMatrix(this.testData2D[name]); + var matrix = this.CreateMatrix(this.TestData2D[name]); var clone = matrix.Clone(); Assert.AreNotSame(matrix, clone); @@ -66,7 +66,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanCloneMatrixUsingICloneable(string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var clone = (Matrix)((ICloneable)matrix).Clone(); Assert.AreNotSame(matrix, clone); @@ -90,7 +90,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanCopyTo(string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var copy = this.CreateMatrix(matrix.RowCount, matrix.ColumnCount); matrix.CopyTo(copy); @@ -108,7 +108,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void CopyToFailsWhenTargetIsNull() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; Matrix target = null; matrix.CopyTo(target); } @@ -117,7 +117,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void CopyToFailsWhenTargetHasMoreRows() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var target = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); matrix.CopyTo(target); } @@ -126,7 +126,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void CopyToFailsWhenTargetHasMoreColumns() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var target = this.CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); matrix.CopyTo(target); } @@ -154,9 +154,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanEquateMatrices(string name) { - var matrix1 = this.CreateMatrix(this.testData2D[name]); - var matrix2 = this.CreateMatrix(this.testData2D[name]); - var matrix3 = this.CreateMatrix(this.testData2D[name].GetLength(0), this.testData2D[name].GetLength(1)); + var matrix1 = this.CreateMatrix(this.TestData2D[name]); + var matrix2 = this.CreateMatrix(this.TestData2D[name]); + var matrix3 = this.CreateMatrix(this.TestData2D[name].GetLength(0), this.TestData2D[name].GetLength(1)); Assert.IsTrue(matrix1.Equals(matrix1)); Assert.IsTrue(matrix1.Equals(matrix2)); Assert.IsFalse(matrix1.Equals(matrix3)); @@ -183,7 +183,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Wide2x3")] public void TestingForEqualityWithNonMatrixReturnsFalse(string name) { - var matrix = this.CreateMatrix(this.testData2D[name]); + var matrix = this.CreateMatrix(this.TestData2D[name]); Assert.IsFalse(matrix.Equals(2)); } @@ -195,8 +195,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Wide2x3")] public void CanTestForEqualityUsingObjectEquals(string name) { - var matrix1 = this.CreateMatrix(this.testData2D[name]); - var matrix2 = this.CreateMatrix(this.testData2D[name]); + var matrix1 = this.CreateMatrix(this.TestData2D[name]); + var matrix2 = this.CreateMatrix(this.TestData2D[name]); Assert.IsTrue(matrix1.Equals((object)matrix2)); } @@ -207,7 +207,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void RangeCheckFails(int i, int j, string name) { - var d = this.testMatrices[name][i, j]; + var d = this.TestMatrices[name][i, j]; } [Test] @@ -219,7 +219,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanClearMatrix() { - var matrix = this.testMatrices["Singular3x3"].Clone(); + var matrix = this.TestMatrices["Singular3x3"].Clone(); matrix.Clear(); for (var i = 0; i < matrix.RowCount; i++) { @@ -237,7 +237,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, "Square3x3")] public void CanGetRow(int rowIndex, string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var row = matrix.Row(rowIndex); Assert.AreEqual(matrix.ColumnCount, row.Count); @@ -251,7 +251,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetRowThrowsArgumentOutOfRangeWithNegativeIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; matrix.Row(-1); } @@ -259,7 +259,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetRowThrowsArgumentOutOfRangeWithOverflowingRowIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; matrix.Row(matrix.RowCount); } @@ -270,7 +270,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, "Square3x3")] public void CanGetRowWithResult(int rowIndex, string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var row = CreateVector(matrix.ColumnCount); matrix.Row(rowIndex, row); @@ -285,7 +285,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void GetRowWithResultFailsWhenResultIsNull() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; matrix.Row(0, null); } @@ -293,7 +293,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetRowWithResultThrowsArgumentOutOfRangeWithNegativeIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var row = CreateVector(matrix.ColumnCount); matrix.Row(-1, row); } @@ -302,7 +302,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetRowWithResultThrowsArgumentOutOfRangeWithOverflowingRowIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var row = CreateVector(matrix.ColumnCount); matrix.Row(matrix.RowCount, row); } @@ -314,7 +314,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, 0, 3, "Square3x3")] public void CanGetRowWithRange(int rowIndex, int start, int length, string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var row = matrix.Row(rowIndex, start, length); Assert.AreEqual(length, row.Count); @@ -328,7 +328,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void GetRowWithRangeResultArgumentExeptionWhenLengthIsZero() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var result = CreateVector(matrix.ColumnCount); matrix.Row(0, 0, 0, result); } @@ -337,7 +337,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void GetRowWithRangeFailsWithTooSmallResultVector() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var result = this.CreateVector(matrix.ColumnCount - 1); matrix.Row(0, 0, 0, result); } @@ -349,7 +349,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, "Square3x3")] public void CanGetColumn(int colIndex, string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var col = matrix.Column(colIndex); Assert.AreEqual(matrix.RowCount, col.Count); @@ -363,7 +363,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetColumnThrowsArgumentOutOfRangeWithNegativeIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; matrix.Column(-1); } @@ -371,7 +371,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetColumnThrowsArgumentOutOfRangeWithOverflowingRowIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; matrix.Column(matrix.ColumnCount); } @@ -382,7 +382,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, "Square3x3")] public void CanGetColumnWithResult(int colIndex, string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var col = CreateVector(matrix.RowCount); matrix.Column(colIndex, col); @@ -397,7 +397,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void GetColumnFailsWhenResultIsNull() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; matrix.Column(0, null); } @@ -405,7 +405,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetColumnWithResultThrowsArgumentOutOfRangeWithNegativeIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var column = CreateVector(matrix.ColumnCount); matrix.Column(-1, column); } @@ -414,7 +414,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentOutOfRangeException))] public void GetColumnWithResultThrowsArgumentOutOfRangeWithOverflowingRowIndex() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var column = CreateVector(matrix.RowCount); matrix.Row(matrix.ColumnCount, column); } @@ -426,7 +426,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, 0, 3, "Square3x3")] public void CanGetColumnWithRange(int colIndex, int start, int length, string name) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var col = matrix.Column(colIndex, start, length); Assert.AreEqual(length, col.Count); @@ -440,7 +440,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void GetColumnWithRangeResultArgumentExeptionWhenLengthIsZero() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var col = CreateVector(matrix.RowCount); matrix.Column(0, 0, 0, col); } @@ -449,7 +449,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentException))] public void GetColumnWithRangeFailsWithTooSmallResultVector() { - var matrix = this.testMatrices["Singular3x3"]; + var matrix = this.TestMatrices["Singular3x3"]; var result = this.CreateVector(matrix.RowCount - 1); matrix.Column(0, 0, matrix.RowCount, result); } @@ -461,7 +461,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, "Square3x3")] public void CanSetRow(int rowIndex, string name) { - var matrix = this.testMatrices[name].Clone(); + var matrix = this.TestMatrices[name].Clone(); matrix.SetRow(rowIndex, CreateVector(matrix.ColumnCount)); for (var i = 0; i < matrix.RowCount; i++) @@ -474,7 +474,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double } else { - Assert.AreEqual(this.testMatrices[name][i, j], matrix[i, j]); + Assert.AreEqual(this.TestMatrices[name][i, j], matrix[i, j]); } } } @@ -487,7 +487,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(2, "Square3x3")] public void CanSetColumn(int colIndex, string name) { - var matrix = this.testMatrices[name].Clone(); + var matrix = this.TestMatrices[name].Clone(); matrix.SetColumn(colIndex, CreateVector(matrix.ColumnCount)); for (var i = 0; i < matrix.RowCount; i++) @@ -500,7 +500,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double } else { - Assert.AreEqual(this.testMatrices[name][i, j], matrix[i, j]); + Assert.AreEqual(this.TestMatrices[name][i, j], matrix[i, j]); } } } @@ -515,7 +515,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void UpperTriangleResult(string name) { - var data = this.testMatrices[name]; + var data = this.TestMatrices[name]; var result = this.CreateMatrix(data.RowCount, data.ColumnCount); var lower = data.UpperTriangle(); for (var i = 0; i < data.RowCount; i++) @@ -539,7 +539,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void UpperTriangleWithResultNullShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; Matrix result = null; data.UpperTriangle(result); } @@ -548,7 +548,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void UpperTriangleWithUnEqualRowsShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; var result = this.CreateMatrix(data.RowCount + 1, data.ColumnCount); data.UpperTriangle(result); } @@ -557,7 +557,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void UpperTriangleWithUnEqualColumnsShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; var result = this.CreateMatrix(data.RowCount, data.ColumnCount + 1); data.UpperTriangle(result); } @@ -565,7 +565,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void StrictlyLowerTriangle() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var lower = data.StrictlyLowerTriangle(); for (var i = 0; i < data.RowCount; i++) @@ -588,7 +588,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void StrictlyLowerTriangleResult() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var lower = this.CreateMatrix(data.RowCount, data.ColumnCount); data.StrictlyLowerTriangle(lower); @@ -613,7 +613,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void StrictlyLowerTriangleWithNullParameterShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; Matrix lower = null; data.StrictlyLowerTriangle(lower); } @@ -622,7 +622,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void StrictlyLowerTriangleWithInvalidColumnNumberShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; var lower = this.CreateMatrix(data.RowCount, data.ColumnCount + 1); data.StrictlyLowerTriangle(lower); } @@ -631,7 +631,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void StrictlyLowerTriangleWithInvalidRowNumberShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; var lower = this.CreateMatrix(data.RowCount + 1, data.ColumnCount); data.StrictlyLowerTriangle(lower); } @@ -640,7 +640,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void StrictlyUpperTriangle() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var lower = data.StrictlyUpperTriangle(); for (var i = 0; i < data.RowCount; i++) @@ -663,7 +663,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void StrictlyUpperTriangleResult() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var lower = this.CreateMatrix(data.RowCount, data.ColumnCount); data.StrictlyUpperTriangle(lower); @@ -688,7 +688,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void StrictlyUpperTriangleWithNullParameterShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; Matrix lower = null; data.StrictlyUpperTriangle(lower); } @@ -697,7 +697,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void StrictlyUpperTriangleWithInvalidColumnNumberShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; var lower = this.CreateMatrix(data.RowCount, data.ColumnCount + 1); data.StrictlyUpperTriangle(lower); } @@ -706,7 +706,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void StrictlyUpperTriangleWithInvalidRowNumberShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; var lower = this.CreateMatrix(data.RowCount + 1, data.ColumnCount); data.StrictlyUpperTriangle(lower); } @@ -720,7 +720,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [MultipleAsserts] public void CanTransposeMatrix(string name) { - var matrix = this.CreateMatrix(this.testData2D[name]); + var matrix = this.CreateMatrix(this.TestData2D[name]); var transpose = matrix.Transpose(); Assert.AreNotSame(matrix, transpose); @@ -742,9 +742,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Wide2x3", new double[] { 1, 2 })] [Row("Singular3x3", null, ExpectedException = typeof(ArgumentNullException))] [Row("Singular3x3", new double[] { 1, 2, 3, 4, 5 }, ExpectedException = typeof(ArgumentException))] - public void SetColumnWithArray(string name, double[] column) + public virtual void SetColumnWithArray(string name, double[] column) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; for (var i = 0; i < matrix.ColumnCount; i++) { matrix.SetColumn(i, column); @@ -759,7 +759,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetColumnArrayWithInvalidColumnIndexShouldThrowException() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; double[] column = { 1, 2, 3 }; matrix.SetColumn(-1, column); } @@ -768,7 +768,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetColumnArrayWithInvalidColumnIndexShouldThrowException2() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; double[] column = { 1, 2, 3 }; matrix.SetColumn(matrix.ColumnCount + 1, column); } @@ -779,9 +779,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Tall3x2", new double[] { 1, 2, 3 })] [Row("Wide2x3", new double[] { 1, 2 })] [Row("Singular3x3", new double[] { 1, 2, 3, 4, 5 }, ExpectedException = typeof(ArgumentException))] - public void SetColumnWithVector(string name, double[] column) + public virtual void SetColumnWithVector(string name, double[] column) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var columnVector = CreateVector(column); for (var i = 0; i < matrix.ColumnCount; i++) { @@ -797,7 +797,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void SetColumnWithNullVectorShouldThrowException() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; Vector columnVector = null; matrix.SetColumn(1, columnVector); } @@ -806,7 +806,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetColumnVectorWithInvalidColumnIndexShouldThrowException() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; var column = this.CreateVector(new double[] { 1, 2, 3 }); matrix.SetColumn(-1, column); } @@ -815,7 +815,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetColumnVectorWithInvalidColumnIndexShouldThrowException2() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; var column = this.CreateVector(new double[] { 1, 2, 3 }); matrix.SetColumn(matrix.ColumnCount + 1, column); } @@ -855,7 +855,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void InsertNullColumnShouldThrowExecption() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; matrix.InsertColumn(0, null); } @@ -883,9 +883,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Wide2x3", new double[] { 1, 2, 3 })] [Row("Singular3x3", null, ExpectedException = typeof(ArgumentNullException))] [Row("Singular3x3", new double[] { 1, 2, 3, 4, 5 }, ExpectedException = typeof(ArgumentException))] - public void SetRowWithArray(string name, double[] row) + public virtual void SetRowWithArray(string name, double[] row) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; for (var i = 0; i < matrix.RowCount; i++) { matrix.SetRow(i, row); @@ -900,7 +900,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetRowArrayWithInvalidRowIndexShouldThrowException() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; double[] row = { 1, 2, 3 }; matrix.SetRow(-1, row); } @@ -909,7 +909,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetRowArrayWithInvalidRowIndexShouldThrowException2() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; double[] row = { 1, 2, 3 }; matrix.SetRow(matrix.RowCount + 1, row); } @@ -920,9 +920,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Tall3x2", new double[] { 1, 2 })] [Row("Wide2x3", new double[] { 1, 2, 3 })] [Row("Singular3x3", new double[] { 1, 2, 3, 4, 5 }, ExpectedException = typeof(ArgumentException))] - public void SetRowWithVector(string name, double[] row) + public virtual void SetRowWithVector(string name, double[] row) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var rowVector = CreateVector(row); for (var i = 0; i < matrix.RowCount; i++) { @@ -938,7 +938,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void SetRowWithNullVectorShouldThrowException() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; Vector rowVector = null; matrix.SetRow(1, rowVector); } @@ -947,7 +947,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetRowVectorWithInvalidRowIndexShouldThrowException() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; var row = this.CreateVector(new double[] { 1, 2, 3 }); matrix.SetRow(-1, row); } @@ -956,7 +956,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentOutOfRangeException] public void SetRowVectorWithInvalidRowIndexShouldThrowException2() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; var row = this.CreateVector(new double[] { 1, 2, 3 }); matrix.SetRow(matrix.RowCount + 1, row); } @@ -972,9 +972,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row(0, 2, -1, 2, ExpectedException = typeof(ArgumentOutOfRangeException))] [Row(0, -1, 0, 2, ExpectedException = typeof(ArgumentException))] [Row(0, 2, 0, -1, ExpectedException = typeof(ArgumentException))] - public void SetSubMatrix(int rowStart, int rowLength, int colStart, int colLength) + public virtual void SetSubMatrix(int rowStart, int rowLength, int colStart, int colLength) { - foreach (var matrix in this.testMatrices.Values) + foreach (var matrix in this.TestMatrices.Values) { var subMatrix = matrix.SubMatrix(0, 2, 0, 2); subMatrix[0, 0] = 10.0; @@ -997,7 +997,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void SetSubMatrixWithNullSubMatrixShouldThrowException() { - var data = this.testMatrices["Square3x3"]; + var data = this.TestMatrices["Square3x3"]; Matrix subMatrix = null; data.SetSubMatrix(0, 2, 0, 2, subMatrix); } @@ -1009,7 +1009,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Tall3x2", new double[] { 1, 2 })] public void SetDiagonalVector(string name, double[] diagonal) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; var vector = CreateVector(diagonal); matrix.SetDiagonal(vector); @@ -1026,7 +1026,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void SetDiagonalWithNullVectorParameterShouldThrowException() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; Vector vector = null; matrix.SetDiagonal(vector); } @@ -1039,7 +1039,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Square3x3", null, ExpectedException = typeof(ArgumentNullException))] public void SetDiagonalArray(string name, double[] diagonal) { - var matrix = this.testMatrices[name]; + var matrix = this.TestMatrices[name]; matrix.SetDiagonal(diagonal); var min = Math.Min(matrix.ColumnCount, matrix.RowCount); Assert.AreEqual(diagonal.Length, min); @@ -1084,7 +1084,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedException(typeof(ArgumentNullException))] public void InsertNullRowShouldThrowExecption() { - var matrix = this.testMatrices["Square3x3"]; + var matrix = this.TestMatrices["Square3x3"]; matrix.InsertRow(0, null); } @@ -1108,7 +1108,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void ToArray() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var array = data.ToArray(); Assert.AreEqual(data.RowCount, array.GetLength(0)); @@ -1127,7 +1127,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void ToColumnWiseArray() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var array = data.ToColumnWiseArray(); Assert.AreEqual(data.RowCount * data.ColumnCount, array.Length); @@ -1145,7 +1145,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void ToRowWiseArray() { - foreach (var data in this.testMatrices.Values) + foreach (var data in this.TestMatrices.Values) { var array = data.ToRowWiseArray(); Assert.AreEqual(data.RowCount * data.ColumnCount, array.Length); @@ -1166,10 +1166,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Square3x3")] [Row("Tall3x2")] [MultipleAsserts] - public void CanPermuteMatrixRows(string name) + public virtual void CanPermuteMatrixRows(string name) { - var matrix = this.CreateMatrix(this.testData2D[name]); - var matrixp = this.CreateMatrix(this.testData2D[name]); + var matrix = this.CreateMatrix(this.TestData2D[name]); + var matrixp = this.CreateMatrix(this.TestData2D[name]); var permutation = new Permutation(new[] { 2, 0, 1 }); matrixp.PermuteRows(permutation); @@ -1191,10 +1191,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Square3x3")] [Row("Wide2x3")] [MultipleAsserts] - public void CanPermuteMatrixColumns(string name) + public virtual void CanPermuteMatrixColumns(string name) { - var matrix = this.CreateMatrix(this.testData2D[name]); - var matrixp = this.CreateMatrix(this.testData2D[name]); + var matrix = this.CreateMatrix(this.TestData2D[name]); + var matrixp = this.CreateMatrix(this.TestData2D[name]); var permutation = new Permutation(new[] { 2, 0, 1 }); matrixp.PermuteColumns(permutation); @@ -1214,8 +1214,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanAppendMatrices() { - var left = this.CreateMatrix(this.testData2D["Singular3x3"]); - var right = this.CreateMatrix(this.testData2D["Tall3x2"]); + var left = this.CreateMatrix(this.TestData2D["Singular3x3"]); + var right = this.CreateMatrix(this.TestData2D["Tall3x2"]); var result = left.Append(right); Assert.AreEqual(left.ColumnCount + right.ColumnCount, result.ColumnCount); Assert.AreEqual(left.RowCount, right.RowCount); @@ -1240,7 +1240,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void CanAppendWithRightParameterNullShouldThrowException() { - var left = this.testMatrices["Square3x3"]; + var left = this.TestMatrices["Square3x3"]; Matrix right = null; left.Append(right); } @@ -1249,8 +1249,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void CanAppendWithResultParameterNullShouldThrowException() { - var left = this.testMatrices["Square3x3"]; - var right = this.testMatrices["Tall3x2"]; + var left = this.TestMatrices["Square3x3"]; + var right = this.TestMatrices["Tall3x2"]; Matrix result = null; left.Append(right, result); } @@ -1259,8 +1259,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void AppendingTwoMatricesWithDifferentRowCountShouldThrowException() { - var left = this.testMatrices["Square3x3"]; - var right = this.testMatrices["Wide2x3"]; + var left = this.TestMatrices["Square3x3"]; + var right = this.TestMatrices["Wide2x3"]; var result = left.Append(right); } @@ -1268,8 +1268,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void AppendingWithInvalidResultMatrixColumnsShouldThrowException() { - var left = this.testMatrices["Square3x3"]; - var right = this.testMatrices["Tall3x2"]; + var left = this.TestMatrices["Square3x3"]; + var right = this.TestMatrices["Tall3x2"]; var result = this.CreateMatrix(3, 2); left.Append(right, result); } @@ -1277,8 +1277,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanStackMatrices() { - var top = this.testMatrices["Square3x3"]; - var bottom = this.testMatrices["Wide2x3"]; + var top = this.TestMatrices["Square3x3"]; + var bottom = this.TestMatrices["Wide2x3"]; var result = top.Stack(bottom); Assert.AreEqual(top.RowCount + bottom.RowCount, result.RowCount); Assert.AreEqual(top.ColumnCount, result.ColumnCount); @@ -1303,7 +1303,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void StackingWithBottomParameterNullShouldThrowException() { - var top = this.testMatrices["Square3x3"]; + var top = this.TestMatrices["Square3x3"]; Matrix bottom = null; var result = this.CreateMatrix(top.RowCount + top.RowCount, top.ColumnCount); top.Stack(bottom, result); @@ -1313,8 +1313,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void StackingWithResultParameterNullShouldThrowException() { - var top = this.testMatrices["Square3x3"]; - var bottom = this.testMatrices["Square3x3"]; + var top = this.TestMatrices["Square3x3"]; + var bottom = this.TestMatrices["Square3x3"]; Matrix result = null; top.Stack(bottom, result); } @@ -1323,8 +1323,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void StackingTwoMatricesWithDifferentColumnsShouldThrowException() { - var top = this.testMatrices["Square3x3"]; - var lower = this.testMatrices["Tall3x2"]; + var top = this.TestMatrices["Square3x3"]; + var lower = this.TestMatrices["Tall3x2"]; var result = this.CreateMatrix(top.RowCount + lower.RowCount, top.ColumnCount); top.Stack(lower, result); } @@ -1333,8 +1333,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void StackingWithInvalidResultMatrixRowsShouldThrowException() { - var top = this.testMatrices["Square3x3"]; - var bottom = this.testMatrices["Wide2x3"]; + var top = this.TestMatrices["Square3x3"]; + var bottom = this.TestMatrices["Wide2x3"]; var result = this.CreateMatrix(1, 3); top.Stack(bottom, result); } @@ -1342,8 +1342,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void CanDiagonallyStackMatrics() { - var top = this.testMatrices["Tall3x2"]; - var bottom = this.testMatrices["Wide2x3"]; + var top = this.TestMatrices["Tall3x2"]; + var bottom = this.TestMatrices["Wide2x3"]; var result = top.DiagonalStack(bottom); Assert.AreEqual(top.RowCount + bottom.RowCount, result.RowCount); Assert.AreEqual(top.ColumnCount + bottom.ColumnCount, result.ColumnCount); @@ -1372,16 +1372,16 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void DiagonalStackWithLowerNullShouldThrowException() { - var top = this.testMatrices["Square3x3"]; + var top = this.TestMatrices["Square3x3"]; Matrix lower = null; top.DiagonalStack(lower); } [Test] - public void CanDiagonallyStackMatricesWithPassingResult() + public virtual void CanDiagonallyStackMatricesWithPassingResult() { - var top = this.testMatrices["Tall3x2"]; - var bottom = this.testMatrices["Wide2x3"]; + var top = this.TestMatrices["Tall3x2"]; + var bottom = this.TestMatrices["Wide2x3"]; var result = this.CreateMatrix(top.RowCount + bottom.RowCount, top.ColumnCount + bottom.ColumnCount); top.DiagonalStack(bottom, result); Assert.AreEqual(top.RowCount + bottom.RowCount, result.RowCount); @@ -1411,8 +1411,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentNullException] public void DiagonalStackWithResultNullShouldThrowException() { - var top = this.testMatrices["Square3x3"]; - var lower = this.testMatrices["Wide2x3"]; + var top = this.TestMatrices["Square3x3"]; + var lower = this.TestMatrices["Wide2x3"]; Matrix result = null; top.DiagonalStack(lower, result); } @@ -1421,61 +1421,61 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void DiagonalStackWithInvalidResultMatrixShouldThrowException() { - var top = this.testMatrices["Square3x3"]; - var lower = this.testMatrices["Wide2x3"]; + var top = this.TestMatrices["Square3x3"]; + var lower = this.TestMatrices["Wide2x3"]; var result = this.CreateMatrix(top.RowCount + lower.RowCount + 2, top.ColumnCount + lower.ColumnCount); top.DiagonalStack(lower, result); } [Test] - public void FrobeniusNorm() + public virtual void FrobeniusNorm() { - var matrix = testMatrices["Square3x3"]; + var matrix = TestMatrices["Square3x3"]; AssertHelpers.AlmostEqual(10.77775486824598, matrix.FrobeniusNorm(), 14); - matrix = testMatrices["Wide2x3"]; + matrix = TestMatrices["Wide2x3"]; AssertHelpers.AlmostEqual(4.79478883789474, matrix.FrobeniusNorm(), 14); - matrix = testMatrices["Tall3x2"]; + matrix = TestMatrices["Tall3x2"]; AssertHelpers.AlmostEqual(7.54122006044115, matrix.FrobeniusNorm(), 14); } [Test] - public void InfinityNorm() + public virtual void InfinityNorm() { - Matrix matrix = testMatrices["Square3x3"]; + Matrix matrix = TestMatrices["Square3x3"]; Assert.AreEqual(16.5, matrix.InfinityNorm()); - matrix = testMatrices["Wide2x3"]; + matrix = TestMatrices["Wide2x3"]; Assert.AreEqual(6.6, matrix.InfinityNorm()); - matrix = testMatrices["Tall3x2"]; + matrix = TestMatrices["Tall3x2"]; Assert.AreEqual(9.9, matrix.InfinityNorm()); } [Test] - public void L1Norm() + public virtual void L1Norm() { - Matrix matrix = testMatrices["Square3x3"]; + Matrix matrix = TestMatrices["Square3x3"]; Assert.AreEqual(12.1, matrix.L1Norm()); - matrix = testMatrices["Wide2x3"]; + matrix = TestMatrices["Wide2x3"]; Assert.AreEqual(5.5, matrix.L1Norm()); - matrix = testMatrices["Tall3x2"]; + matrix = TestMatrices["Tall3x2"]; Assert.AreEqual(8.8, matrix.L1Norm()); } [Test] - public void L2Norm() + public virtual void L2Norm() { - var matrix = testMatrices["Square3x3"]; + var matrix = TestMatrices["Square3x3"]; AssertHelpers.AlmostEqual(10.391347375312632, matrix.L2Norm(), 14); - matrix = testMatrices["Wide2x3"]; + matrix = TestMatrices["Wide2x3"]; AssertHelpers.AlmostEqual(4.7540849434107635, matrix.L2Norm(), 14); - matrix = testMatrices["Tall3x2"]; + matrix = TestMatrices["Tall3x2"]; AssertHelpers.AlmostEqual(7.182727033856683, matrix.L2Norm(), 14); } } diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/BiCgStabTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/BiCgStabTest.cs new file mode 100644 index 00000000..1e851a84 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/BiCgStabTest.cs @@ -0,0 +1,205 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Iterative +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers; + using LinearAlgebra.Double.Solvers.Iterative; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public class BiCgStabTest + { + private const double ConvergenceBoundary = 1e-10; + private const int MaximumIterations = 1000; + + [Test] + [ExpectedArgumentException] + public void SolveWideMatrix() + { + var matrix = new SparseMatrix(2, 3); + Vector input = new DenseVector(2); + + var solver = new BiCgStab(); + solver.Solve(matrix, input); + } + + [Test] + [ExpectedArgumentException] + public void SolveLongMatrix() + { + var matrix = new SparseMatrix(3, 2); + Vector input = new DenseVector(3); + + var solver = new BiCgStab(); + solver.Solve(matrix, input); + } + + [Test] + [MultipleAsserts] + public void SolveUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new BiCgStab(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + + // Scale it with a funny number + matrix.Multiply(System.Math.PI); + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new BiCgStab(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolvePoissonMatrixAndBackMultiply() + { + // Create the matrix + var matrix = new SparseMatrix(100); + + // Assemble the matrix. We assume we're solving the Poisson equation + // on a rectangular 10 x 10 grid + const int GridSize = 10; + + // The pattern is: + // 0 .... 0 -1 0 0 0 0 0 0 0 0 -1 4 -1 0 0 0 0 0 0 0 0 -1 0 0 ... 0 + for (var i = 0; i < matrix.RowCount; i++) + { + // Insert the first set of -1's + if (i > (GridSize - 1)) + { + matrix[i, i - GridSize] = -1; + } + + // Insert the second set of -1's + if (i > 0) + { + matrix[i, i - 1] = -1; + } + + // Insert the centerline values + matrix[i, i] = 4; + + // Insert the first trailing set of -1's + if (i < matrix.RowCount - 1) + { + matrix[i, i + 1] = -1; + } + + // Insert the second trailing set of -1's + if (i < matrix.RowCount - GridSize) + { + matrix[i, i + GridSize] = -1; + } + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new BiCgStab(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue(System.Math.Abs(y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/GpBiCgTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/GpBiCgTest.cs new file mode 100644 index 00000000..a4154c7b --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/GpBiCgTest.cs @@ -0,0 +1,207 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Iterative +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers; + using LinearAlgebra.Double.Solvers.Iterative; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public class GpBiCgTest + { + private const double ConvergenceBoundary = 1e-10; + private const int MaximumIterations = 1000; + + [Test] + [ExpectedArgumentException] + public void SolveWideMatrix() + { + var matrix = new SparseMatrix(2, 3); + Vector input = new DenseVector(2); + + var solver = new GpBiCg(); + solver.Solve(matrix, input); + } + + [Test] + [ExpectedArgumentException] + public void SolveLongMatrix() + { + var matrix = new SparseMatrix(3, 2); + Vector input = new DenseVector(3); + + var solver = new GpBiCg(); + solver.Solve(matrix, input); + } + + [Test] + [MultipleAsserts] + public void SolveUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new GpBiCg(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + + // Scale it with a funny number + matrix.Multiply(System.Math.PI); + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + + var solver = new GpBiCg(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolvePoissonMatrixAndBackMultiply() + { + // Create the matrix + var matrix = new SparseMatrix(100); + + // Assemble the matrix. We assume we're solving the Poisson equation + // on a rectangular 10 x 10 grid + const int GridSize = 10; + + // The pattern is: + // 0 .... 0 -1 0 0 0 0 0 0 0 0 -1 4 -1 0 0 0 0 0 0 0 0 -1 0 0 ... 0 + for (var i = 0; i < matrix.RowCount; i++) + { + // Insert the first set of -1's + if (i > (GridSize - 1)) + { + matrix[i, i - GridSize] = -1; + } + + // Insert the second set of -1's + if (i > 0) + { + matrix[i, i - 1] = -1; + } + + // Insert the centerline values + matrix[i, i] = 4; + + // Insert the first trailing set of -1's + if (i < matrix.RowCount - 1) + { + matrix[i, i + 1] = -1; + } + + // Insert the second trailing set of -1's + if (i < matrix.RowCount - GridSize) + { + matrix[i, i + GridSize] = -1; + } + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + + var solver = new GpBiCg(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue(System.Math.Abs(y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/MlkBiCgStabTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/MlkBiCgStabTest.cs new file mode 100644 index 00000000..92883217 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/MlkBiCgStabTest.cs @@ -0,0 +1,205 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Iterative +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers; + using LinearAlgebra.Double.Solvers.Iterative; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public class MlkBiCgStabTest + { + private const double ConvergenceBoundary = 1e-10; + private const int MaximumIterations = 1000; + + [Test] + [ExpectedArgumentException] + public void SolveWideMatrix() + { + var matrix = new SparseMatrix(2, 3); + Vector input = new DenseVector(2); + + var solver = new MlkBiCgStab(); + solver.Solve(matrix, input); + } + + [Test] + [ExpectedArgumentException] + public void SolveLongMatrix() + { + var matrix = new SparseMatrix(3, 2); + Vector input = new DenseVector(3); + + var solver = new MlkBiCgStab(); + solver.Solve(matrix, input); + } + + [Test] + [MultipleAsserts] + public void SolveUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + + var solver = new MlkBiCgStab(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + + // Scale it with a funny number + matrix.Multiply(System.Math.PI); + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new MlkBiCgStab(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolvePoissonMatrixAndBackMultiply() + { + // Create the matrix + var matrix = new SparseMatrix(100); + // Assemble the matrix. We assume we're solving the Poisson equation + // on a rectangular 10 x 10 grid + const int GridSize = 10; + + // The pattern is: + // 0 .... 0 -1 0 0 0 0 0 0 0 0 -1 4 -1 0 0 0 0 0 0 0 0 -1 0 0 ... 0 + for (var i = 0; i < matrix.RowCount; i++) + { + // Insert the first set of -1's + if (i > (GridSize - 1)) + { + matrix[i, i - GridSize] = -1; + } + + // Insert the second set of -1's + if (i > 0) + { + matrix[i, i - 1] = -1; + } + + // Insert the centerline values + matrix[i, i] = 4; + + // Insert the first trailing set of -1's + if (i < matrix.RowCount - 1) + { + matrix[i, i + 1] = -1; + } + + // Insert the second trailing set of -1's + if (i < matrix.RowCount - GridSize) + { + matrix[i, i + GridSize] = -1; + } + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new MlkBiCgStab(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue(System.Math.Abs(y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/TFQMRTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/TFQMRTest.cs new file mode 100644 index 00000000..20267552 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Iterative/TFQMRTest.cs @@ -0,0 +1,204 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Iterative +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers; + using LinearAlgebra.Double.Solvers.Iterative; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public class TFQMRTest + { + private const double ConvergenceBoundary = 1e-10; + private const int MaximumIterations = 1000; + + [Test] + [ExpectedArgumentException] + public void SolveWideMatrix() + { + var matrix = new SparseMatrix(2, 3); + Vector input = new DenseVector(2); + + var solver = new TFQMR(); + solver.Solve(matrix, input); + } + + [Test] + [ExpectedArgumentException] + public void SolveLongMatrix() + { + var matrix = new SparseMatrix(3, 2); + Vector input = new DenseVector(3); + + var solver = new TFQMR(); + solver.Solve(matrix, input); + } + + [Test] + [MultipleAsserts] + public void SolveUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + + var solver = new TFQMR(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = new SparseMatrix(100); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, i] = 1.0; + } + // Scale it with a funny number + matrix.Multiply(System.Math.PI); + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new TFQMR(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue((y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolvePoissonMatrixAndBackMultiply() + { + // Create the matrix + var matrix = new SparseMatrix(100); + // Assemble the matrix. We assume we're solving the Poisson equation + // on a rectangular 10 x 10 grid + const int GridSize = 10; + + // The pattern is: + // 0 .... 0 -1 0 0 0 0 0 0 0 0 -1 4 -1 0 0 0 0 0 0 0 0 -1 0 0 ... 0 + for (var i = 0; i < matrix.RowCount; i++) + { + // Insert the first set of -1's + if (i > (GridSize - 1)) + { + matrix[i, i - GridSize] = -1; + } + + // Insert the second set of -1's + if (i > 0) + { + matrix[i, i - 1] = -1; + } + + // Insert the centerline values + matrix[i, i] = 4; + + // Insert the first trailing set of -1's + if (i < matrix.RowCount - 1) + { + matrix[i, i + 1] = -1; + } + + // Insert the second trailing set of -1's + if (i < matrix.RowCount - GridSize) + { + matrix[i, i + GridSize] = -1; + } + } + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, 1); + + // Create an iteration monitor which will keep track of iterative convergence + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(MaximumIterations), + new ResidualStopCriterium(ConvergenceBoundary), + new DivergenceStopCriterium(), + new FailureStopCriterium() + }); + var solver = new TFQMR(monitor); + + // Solve equation Ax = y + var x = solver.Solve(matrix, y); + + // Now compare the results + Assert.IsNotNull(x, "#02"); + Assert.AreEqual(y.Count, x.Count, "#03"); + + // Back multiply the vector + var z = matrix.Multiply(x); + + // Check that the solution converged + Assert.IsTrue(monitor.Status is CalculationConverged, "#04"); + + // Now compare the vectors + for (var i = 0; i < y.Count; i++) + { + Assert.IsTrue(System.Math.Abs(y[i] - z[i]).IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/IteratorTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/IteratorTest.cs new file mode 100644 index 00000000..680fbbdb --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/IteratorTest.cs @@ -0,0 +1,353 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers +{ + using System; + using System.Collections.Generic; + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public class IteratorTest + { + [Test] + [MultipleAsserts] + public void CreateWithNullCollection() + { + var iterator = new Iterator(null); + Assert.IsNotNull(iterator, "Should have an iterator"); + Assert.AreEqual(0, iterator.NumberOfCriteria, "There shouldn't be any criteria"); + } + + [Test] + [MultipleAsserts] + public void CreateWithEmptyCollection() + { + var iterator = new Iterator(new IIterationStopCriterium[] { }); + Assert.IsNotNull(iterator, "Should have an iterator"); + Assert.AreEqual(0, iterator.NumberOfCriteria, "There shouldn't be any criteria"); + } + + [Test] + [MultipleAsserts] + public void CreateWithCollectionWithNulls() + { + var iterator = new Iterator(new IIterationStopCriterium[] { null, null }); + Assert.IsNotNull(iterator, "Should have an iterator"); + Assert.AreEqual(0, iterator.NumberOfCriteria, "There shouldn't be any criteria"); + } + + [Test] + [ExpectedArgumentException] + public void CreateWithDuplicates() + { + new Iterator(new IIterationStopCriterium[] + { + new FailureStopCriterium(), + new FailureStopCriterium() + }); + } + + [Test] + [MultipleAsserts] + public void CreateWithCollection() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(criteria); + Assert.IsNotNull(iterator, "Should have an iterator"); + + // Check that we have all the criteria + Assert.AreEqual(criteria.Count, iterator.NumberOfCriteria, "Incorrect criterium count"); + var enumerator = iterator.StoredStopCriteria; + while (enumerator.MoveNext()) + { + var criterium = enumerator.Current; + Assert.IsTrue(criteria.Exists( c => ReferenceEquals(c, criterium)), "Criterium missing"); + } + } + + [Test] + [ExpectedArgumentNullException] + public void AddWithNullStopCriterium() + { + var iterator = new Iterator(); + iterator.Add(null); + } + + [Test] + [ExpectedArgumentException] + public void AddWithExistingStopCriterium() + { + var iterator = new Iterator(); + iterator.Add(new FailureStopCriterium()); + Assert.AreEqual(1, iterator.NumberOfCriteria, "Incorrect criterium count"); + + iterator.Add(new FailureStopCriterium()); + } + + [Test] + [MultipleAsserts] + public void Add() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(); + Assert.AreEqual(0, iterator.NumberOfCriteria, "Incorrect criterium count"); + + foreach (var criterium in criteria) + { + iterator.Add(criterium); + Assert.IsTrue(iterator.Contains(criterium), "Missing criterium"); + } + + // Check that we have all the criteria + Assert.AreEqual(criteria.Count, iterator.NumberOfCriteria, "Incorrect criterium count"); + var enumerator = iterator.StoredStopCriteria; + while (enumerator.MoveNext()) + { + var criterium = enumerator.Current; + Assert.IsTrue(criteria.Exists( c => ReferenceEquals(c, criterium)), "Criterium missing"); + } + } + + [Test] + [ExpectedArgumentNullException] + public void RemoveWithNullStopCriterium() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(criteria); + Assert.AreEqual(criteria.Count, iterator.NumberOfCriteria, "Incorrect criterium count"); + + iterator.Remove(null); + } + + [Test] + [MultipleAsserts] + public void RemoveWithNonExistingStopCriterium() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + }; + var iterator = new Iterator(criteria); + Assert.AreEqual(criteria.Count, iterator.NumberOfCriteria, "Incorrect criterium count"); + + iterator.Remove(new ResidualStopCriterium()); + Assert.AreEqual(criteria.Count, iterator.NumberOfCriteria, "Incorrect criterium count"); + } + + [Test] + [MultipleAsserts] + public void Remove() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(criteria); + Assert.AreEqual(criteria.Count, iterator.NumberOfCriteria, "Incorrect criterium count"); + + foreach (var criterium in criteria) + { + iterator.Remove(criterium); + Assert.IsFalse(iterator.Contains(criterium), "Did not remove the criterium"); + } + } + + [Test] + [ExpectedArgumentException] + public void DetermineStatusWithoutStopCriteria() + { + var iterator = new Iterator(); + iterator.DetermineStatus(0, + new DenseVector(3, 4), + new DenseVector(3, 5), + new DenseVector(3, 6)); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void DetermineStatusWithNegativeIterationNumber() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(criteria); + + iterator.DetermineStatus(-1, + new DenseVector(3, 4), + new DenseVector(3, 5), + new DenseVector(3, 6)); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullSolutionVector() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(criteria); + + iterator.DetermineStatus(1, + null, + new DenseVector(3, 5), + new DenseVector(3, 6)); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullSourceVector() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(criteria); + + iterator.DetermineStatus(1, + new DenseVector(3, 5), + null, + new DenseVector(3, 6)); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullResidualVector() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + var iterator = new Iterator(criteria); + + iterator.DetermineStatus(1, + new DenseVector(3, 4), + new DenseVector(3, 5), + null); + } + + [Test] + [MultipleAsserts] + public void DetermineStatus() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(1) + }; + + var iterator = new Iterator(criteria); + + // First step, nothing should happen. + iterator.DetermineStatus(0, + new DenseVector(3, 4), + new DenseVector(3, 4), + new DenseVector(3, 4)); + Assert.IsInstanceOfType(typeof(CalculationRunning), iterator.Status, "Incorrect status"); + + // Second step, should run out of iterations. + iterator.DetermineStatus(1, + new DenseVector(3, 4), + new DenseVector(3, 4), + new DenseVector(3, 4)); + Assert.IsInstanceOfType(typeof(CalculationStoppedWithoutConvergence), iterator.Status, "Incorrect status"); + } + + [Test] + [MultipleAsserts] + public void ResetToPrecalculationState() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(1) + }; + + var iterator = new Iterator(criteria); + + // First step, nothing should happen. + iterator.DetermineStatus(0, + new DenseVector(3, 4), + new DenseVector(3, 4), + new DenseVector(3, 4)); + Assert.IsInstanceOfType(typeof(CalculationRunning), iterator.Status, "Incorrect status"); + + iterator.ResetToPrecalculationState(); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), iterator.Status, "Incorrect status"); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), criteria[0].Status, "Incorrect status"); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), criteria[1].Status, "Incorrect status"); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), criteria[2].Status, "Incorrect status"); + } + + [Test] + [MultipleAsserts] + public void Clone() + { + var criteria = new List + { + new FailureStopCriterium(), + new DivergenceStopCriterium(), + new IterationCountStopCriterium(), + new ResidualStopCriterium() + }; + + var iterator = new Iterator(criteria); + + var clonedIterator = iterator.Clone(); + Assert.IsInstanceOfType(typeof(Iterator), clonedIterator, "Incorrect type"); + + var clone = clonedIterator as Iterator; + Assert.IsNotNull(clone); + // ReSharper disable PossibleNullReferenceException + Assert.AreEqual(iterator.NumberOfCriteria, clone.NumberOfCriteria, "Incorrect criterium count"); + // ReSharper restore PossibleNullReferenceException + + var enumerator = clone.StoredStopCriteria; + while (enumerator.MoveNext()) + { + var criterium = enumerator.Current; + Assert.IsTrue(criteria.Exists(c => c.GetType().Equals(criterium.GetType())), "Criterium missing"); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/DiagonalTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/DiagonalTest.cs new file mode 100644 index 00000000..f15492db --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/DiagonalTest.cs @@ -0,0 +1,30 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Preconditioners +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Preconditioners; + using MbUnit.Framework; + + [TestFixture] + public sealed class DiagonalTest : PreconditionerTest + { + internal override IPreConditioner CreatePreconditioner() + { + return new Diagonal(); + } + + protected override void CheckResult(IPreConditioner preconditioner, SparseMatrix matrix, Vector vector, Vector result) + { + Assert.AreEqual(typeof(Diagonal), preconditioner.GetType(), "#01"); + + // Compute M * result = product + // compare vector and product. Should be equal + Vector product = new DenseVector(result.Count); + matrix.Multiply(result, product); + + for (var i = 0; i < product.Count; i++) + { + Assert.IsTrue(vector[i].AlmostEqual(product[i], -Epsilon.Magnitude()), "#02-" + i); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IluptElementSorterTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IluptElementSorterTest.cs new file mode 100644 index 00000000..649a6a34 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IluptElementSorterTest.cs @@ -0,0 +1,502 @@ +// NOTE: This class file Build Action is not set to Compile by default. Because IlutpElementSorter class is internal. If you want +// NOTE: to test IlutpElementSorter you should make it public, set Build Action=Compile of this file (in properties) and run tets. +// NOTE: After all tests passed please do all actions vice versa. IlutpElementSorter class is only for internal usage. + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Preconditioners +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Preconditioners; + using MbUnit.Framework; + + [TestFixture] + public sealed class IluptElementSorterTest + { + [Test] + [MultipleAsserts] + public void HeapSortWithIncreasingIntergerArray() + { + var sortedIndices = new [] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + Assert.AreEqual(sortedIndices.Length - 1 - i, sortedIndices[i], "#01-" + i); + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithDecreasingIntegerArray() + { + var sortedIndices = new [] { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + Assert.AreEqual(sortedIndices.Length - 1 - i, sortedIndices[i], "#01-" + i); + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithRandomIntegerArray() + { + var sortedIndices = new []{ 5, 2, 8, 6, 0, 4, 1, 7, 3, 9 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + Assert.AreEqual(sortedIndices.Length - 1 - i, sortedIndices[i], "#01-" + i); + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithDuplicateEntries() + { + var sortedIndices = new []{ 1, 1, 1, 1, 2, 2, 2, 2, 3, 4 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(4, sortedIndices[i], "#01-" + i); + } + else + { + if (i == 1) + { + Assert.AreEqual(3, sortedIndices[i], "#01-" + i); + } + else + { + if (i < 6) + { + if (sortedIndices[i] != 2) + { + Assert.Fail("#01-" + i); + } + } + else + { + if (sortedIndices[i] != 1) + { + Assert.Fail("#01-" + i); + } + } + } + } + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithSpecialConstructedIntegerArray() + { + var sortedIndices = new []{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(1, sortedIndices[i], "#01-" + i); + break; + } + } + + sortedIndices = new []{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(1, sortedIndices[i], "#02-" + i); + break; + } + } + + sortedIndices = new []{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(1, sortedIndices[i], "#03-" + i); + break; + } + } + + sortedIndices = new []{ 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 }; + IlutpElementSorter.SortIntegersDecreasing(sortedIndices); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 9) + { + Assert.AreEqual(0, sortedIndices[i], "#04-" + i); + break; + } + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithIncreasingDoubleArray() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 0; + values[1] = 1; + values[2] = 2; + values[3] = 3; + values[4] = 4; + values[5] = 5; + values[6] = 6; + values[7] = 7; + values[8] = 8; + values[9] = 9; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + Assert.AreEqual(sortedIndices.Length - 1 - i, sortedIndices[i], "#01-" + i); + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithDecreasingDoubleArray() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 9; + values[1] = 8; + values[2] = 7; + values[3] = 6; + values[4] = 5; + values[5] = 4; + values[6] = 3; + values[7] = 2; + values[8] = 1; + values[9] = 0; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + Assert.AreEqual(i, sortedIndices[i], "#01-" + i); + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithRandomDoubleArray() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 5; + values[1] = 2; + values[2] = 8; + values[3] = 6; + values[4] = 0; + values[5] = 4; + values[6] = 1; + values[7] = 7; + values[8] = 3; + values[9] = 9; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + switch (i) + { + case 0: + Assert.AreEqual(9, sortedIndices[i], "#01-" + i); + break; + case 1: + Assert.AreEqual(2, sortedIndices[i], "#01-" + i); + break; + case 2: + Assert.AreEqual(7, sortedIndices[i], "#01-" + i); + break; + case 3: + Assert.AreEqual(3, sortedIndices[i], "#01-" + i); + break; + case 4: + Assert.AreEqual(0, sortedIndices[i], "#01-" + i); + break; + case 5: + Assert.AreEqual(5, sortedIndices[i], "#01-" + i); + break; + case 6: + Assert.AreEqual(8, sortedIndices[i], "#01-" + i); + break; + case 7: + Assert.AreEqual(1, sortedIndices[i], "#01-" + i); + break; + case 8: + Assert.AreEqual(6, sortedIndices[i], "#01-" + i); + break; + case 9: + Assert.AreEqual(4, sortedIndices[i], "#01-" + i); + break; + } + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithDuplicateDoubleEntries() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 1; + values[1] = 1; + values[2] = 1; + values[3] = 1; + values[4] = 2; + values[5] = 2; + values[6] = 2; + values[7] = 2; + values[8] = 3; + values[9] = 4; + + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(9, sortedIndices[i], "#01-" + i); + } + else + { + if (i == 1) + { + Assert.AreEqual(8, sortedIndices[i], "#01-" + i); + } + else + { + if (i < 6) + { + if ((sortedIndices[i] != 4) && + (sortedIndices[i] != 5) && + (sortedIndices[i] != 6) && + (sortedIndices[i] != 7)) + { + Assert.Fail("#01-" + i); + } + } + else + { + if ((sortedIndices[i] != 0) && + (sortedIndices[i] != 1) && + (sortedIndices[i] != 2) && + (sortedIndices[i] != 3)) + { + Assert.Fail("#01-" + i); + } + } + } + } + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithSpecialConstructedDoubleArray() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 0; + values[1] = 0; + values[2] = 0; + values[3] = 0; + values[4] = 0; + values[5] = 1; + values[6] = 0; + values[7] = 0; + values[8] = 0; + values[9] = 0; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(5, sortedIndices[i], "#01-" + i); + break; + } + } + + values[0] = 1; + values[1] = 0; + values[2] = 0; + values[3] = 0; + values[4] = 0; + values[5] = 0; + values[6] = 0; + values[7] = 0; + values[8] = 0; + values[9] = 0; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(0, sortedIndices[i], "#02-" + i); + break; + } + } + + values[0] = 0; + values[1] = 0; + values[2] = 0; + values[3] = 0; + values[4] = 0; + values[5] = 0; + values[6] = 0; + values[7] = 0; + values[8] = 0; + values[9] = 1; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 0) + { + Assert.AreEqual(9, sortedIndices[i], "#03-" + i); + break; + } + } + + values[0] = 1; + values[1] = 1; + values[2] = 1; + values[3] = 0; + values[4] = 1; + values[5] = 1; + values[6] = 1; + values[7] = 1; + values[8] = 1; + values[9] = 1; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length; i++) + { + if (i == 9) + { + Assert.AreEqual(3, sortedIndices[i], "#04-" + i); + break; + } + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithIncreasingDoubleArrayWithLowerBound() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 0; + values[1] = 1; + values[2] = 2; + values[3] = 3; + values[4] = 4; + values[5] = 5; + values[6] = 6; + values[7] = 7; + values[8] = 8; + values[9] = 9; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + + IlutpElementSorter.SortDoubleIndicesDecreasing(4, sortedIndices.Length - 1, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length - 4; i++) + { + Assert.AreEqual(sortedIndices.Length - 1 - i, sortedIndices[i], "#01-" + i); + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithIncreasingDoubleArrayWithUpperBound() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 0; + values[1] = 1; + values[2] = 2; + values[3] = 3; + values[4] = 4; + values[5] = 5; + values[6] = 6; + values[7] = 7; + values[8] = 8; + values[9] = 9; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + + IlutpElementSorter.SortDoubleIndicesDecreasing(0, sortedIndices.Length - 5, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length - 5; i++) + { + Assert.AreEqual(sortedIndices.Length - 5 - i, sortedIndices[i], "#01-" + i); + } + } + + [Test] + [MultipleAsserts] + public void HeapSortWithIncreasingDoubleArrayWithLowerAndUpperBound() + { + var sortedIndices = new int[10]; + Vector values = new DenseVector(10); + values[0] = 0; + values[1] = 1; + values[2] = 2; + values[3] = 3; + values[4] = 4; + values[5] = 5; + values[6] = 6; + values[7] = 7; + values[8] = 8; + values[9] = 9; + for (var i = 0; i < sortedIndices.Length; i++) + { + sortedIndices[i] = i; + } + + IlutpElementSorter.SortDoubleIndicesDecreasing(2, sortedIndices.Length - 3, sortedIndices, values); + for (var i = 0; i < sortedIndices.Length - 4; i++) + { + Assert.AreEqual(sortedIndices.Length - 3 - i, sortedIndices[i], "#01-" + i); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IlutpTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IlutpTest.cs new file mode 100644 index 00000000..ce12fe37 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IlutpTest.cs @@ -0,0 +1,248 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Preconditioners +{ + using System; + using System.Reflection; + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Preconditioners; + using MbUnit.Framework; + + [TestFixture] + public sealed class IlutpPreconditionerTest : PreconditionerTest + { + private double _dropTolerance = 0.1; + private double _fillLevel = 1.0; + private double _pivotTolerance = 1.0; + + [SetUp] + public void Setup() + { + _dropTolerance = 0.1; + _fillLevel = 1.0; + _pivotTolerance = 1.0; + } + + private static T GetMethod(Ilutp ilutp, string methodName) + { + var type = ilutp.GetType(); + var methodInfo = type.GetMethod(methodName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, + null, + CallingConventions.Standard, + new Type[0], + null); + var obj = methodInfo.Invoke(ilutp, null); + return (T)obj; + } + + private static SparseMatrix GetUpperTriangle(Ilutp ilutp) + { + return GetMethod(ilutp, "UpperTriangle"); + } + + private static SparseMatrix GetLowerTriangle(Ilutp ilutp) + { + return GetMethod(ilutp, "LowerTriangle"); + } + + private static int[] GetPivots(Ilutp ilutp) + { + return GetMethod(ilutp, "Pivots"); + } + + private static SparseMatrix CreateReverseUnitMatrix(int size) + { + var matrix = new SparseMatrix(size); + for (var i = 0; i < size; i++) + { + matrix[i, size - 1 - i] = 2; + } + + return matrix; + } + + private Ilutp InternalCreatePreconditioner() + { + var result = new Ilutp + { + DropTolerance = _dropTolerance, + FillLevel = _fillLevel, + PivotTolerance = _pivotTolerance + }; + return result; + } + + internal override IPreConditioner CreatePreconditioner() + { + _pivotTolerance = 0; + _dropTolerance = 0.0; + _fillLevel = 100; + return InternalCreatePreconditioner(); + } + + protected override void CheckResult(IPreConditioner preconditioner, SparseMatrix matrix, Vector vector, Vector result) + { + Assert.AreEqual(typeof(Ilutp), preconditioner.GetType(), "#01"); + + // Compute M * result = product + // compare vector and product. Should be equal + Vector product = new DenseVector(result.Count); + matrix.Multiply(result, product); + for (var i = 0; i < product.Count; i++) + { + Assert.IsTrue(vector[i].AlmostEqual(product[i], -Epsilon.Magnitude()), "#02-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveReturningOldVectorWithoutPivoting() + { + const int Size = 10; + + var newMatrix = CreateUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + + // set the pivot tolerance to zero so we don't pivot + _pivotTolerance = 0.0; + _dropTolerance = 0.0; + _fillLevel = 100; + var preconditioner = CreatePreconditioner(); + preconditioner.Initialize(newMatrix); + Vector result = new DenseVector(vector.Count); + preconditioner.Approximate(vector, result); + CheckResult(preconditioner, newMatrix, vector, result); + } + + [Test] + [MultipleAsserts] + public void SolveReturningOldVectorWithPivoting() + { + const int Size = 10; + var newMatrix = CreateUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + + // Set the pivot tolerance to 1 so we always pivot (if necessary) + _pivotTolerance = 1.0; + _dropTolerance = 0.0; + _fillLevel = 100; + var preconditioner = CreatePreconditioner(); + preconditioner.Initialize(newMatrix); + Vector result = new DenseVector(vector.Count); + preconditioner.Approximate(vector, result); + CheckResult(preconditioner, newMatrix, vector, result); + } + + [Test] + [MultipleAsserts] + public void CompareWithOriginalDenseMatrixWithoutPivoting() + { + var sparseMatrix = new SparseMatrix(3); + sparseMatrix[0, 0] = -1; + sparseMatrix[0, 1] = 5; + sparseMatrix[0, 2] = 6; + sparseMatrix[1, 0] = 3; + sparseMatrix[1, 1] = -6; + sparseMatrix[1, 2] = 1; + sparseMatrix[2, 0] = 6; + sparseMatrix[2, 1] = 8; + sparseMatrix[2, 2] = 9; + var ilu = new Ilutp + { + PivotTolerance = 0.0, + DropTolerance = 0, + FillLevel = 10 + }; + ilu.Initialize(sparseMatrix); + var l = GetLowerTriangle(ilu); + + // Assert l is lower triagonal + for (var i = 0; i < l.RowCount; i++) + { + for (var j = i + 1; j < l.RowCount; j++) + { + Assert.IsTrue(0.0.AlmostEqual(l[i,j], -Epsilon.Magnitude()), "#01-" + i + "-" + j); + } + } + + var u = GetUpperTriangle(ilu); + + // Assert u is upper triagonal + for (var i = 0; i < u.RowCount; i++) + { + for (var j = 0; j < i; j++) + { + Assert.IsTrue(0.0.AlmostEqual(u[i,j], -Epsilon.Magnitude()), "#02-" + i + "-" + j); + } + } + + var original = l.Multiply(u); + for (var i = 0; i < sparseMatrix.RowCount; i++) + { + for (var j = 0; j < sparseMatrix.ColumnCount; j++) + { + Assert.IsTrue(sparseMatrix[i,j].AlmostEqual(original[i, j], -Epsilon.Magnitude()), "#03-" + i + "-" + j); + } + } + } + + [Test] + [MultipleAsserts] + public void CompareWithOriginalDenseMatrixWithPivoting() + { + var sparseMatrix = new SparseMatrix(3); + sparseMatrix[0, 0] = -1; + sparseMatrix[0, 1] = 5; + sparseMatrix[0, 2] = 6; + sparseMatrix[1, 0] = 3; + sparseMatrix[1, 1] = -6; + sparseMatrix[1, 2] = 1; + sparseMatrix[2, 0] = 6; + sparseMatrix[2, 1] = 8; + sparseMatrix[2, 2] = 9; + var ilu = new Ilutp + { + PivotTolerance = 1.0, + DropTolerance = 0, + FillLevel = 10 + }; + ilu.Initialize(sparseMatrix); + var l = GetLowerTriangle(ilu); + var u = GetUpperTriangle(ilu); + var pivots = GetPivots(ilu); + var p = new SparseMatrix(l.RowCount); + for (var i = 0; i < p.RowCount; i++) + { + p[i, pivots[i]] = 1.0; + } + + var temp = l.Multiply(u); + var original = temp.Multiply(p); + for (var i = 0; i < sparseMatrix.RowCount; i++) + { + for (var j = 0; j < sparseMatrix.ColumnCount; j++) + { + Assert.IsTrue(sparseMatrix[i, j].AlmostEqual(original[i, j], -Epsilon.Magnitude()), "#01-" + i + "-" + j); + } + } + } + + [Test] + [MultipleAsserts] + public void SolveWithPivoting() + { + const int Size = 10; + var newMatrix = CreateReverseUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + var preconditioner = new Ilutp + { + PivotTolerance = 1.0, + DropTolerance = 0, + FillLevel = 10 + }; + preconditioner.Initialize(newMatrix); + Vector result = new DenseVector(vector.Count); + preconditioner.Approximate(vector, result); + CheckResult(preconditioner, newMatrix, vector, result); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IncompleteLUTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IncompleteLUTest.cs new file mode 100644 index 00000000..740e1c66 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/IncompleteLUTest.cs @@ -0,0 +1,82 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Preconditioners +{ + using System; + using System.Reflection; + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Preconditioners; + using MbUnit.Framework; + + [TestFixture] + public sealed class IncompleteLUFactorizationTest : PreconditionerTest + { + private static T GetMethod(IncompleteLU ilu, string methodName) + { + var type = ilu.GetType(); + var methodInfo = type.GetMethod(methodName, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, + null, + CallingConventions.Standard, + new Type[0], + null); + var obj = methodInfo.Invoke(ilu, null); + return (T)obj; + } + + private static Matrix GetUpperTriangle(IncompleteLU ilu) + { + return GetMethod(ilu, "UpperTriangle"); + } + + private static Matrix GetLowerTriangle(IncompleteLU ilu) + { + return GetMethod(ilu, "LowerTriangle"); + } + + internal override IPreConditioner CreatePreconditioner() + { + return new IncompleteLU(); + } + + protected override void CheckResult(IPreConditioner preconditioner, SparseMatrix matrix, Vector vector, Vector result) + { + Assert.AreEqual(typeof(IncompleteLU), preconditioner.GetType(), "#01"); + + // Compute M * result = product + // compare vector and product. Should be equal + Vector product = new DenseVector(result.Count); + matrix.Multiply(result, product); + + for (var i = 0; i < product.Count; i++) + { + Assert.IsTrue(vector[i].AlmostEqual(product[i], -Epsilon.Magnitude()), "#02-" + i); + } + } + + [Test] + [MultipleAsserts] + public void CompareWithOriginalDenseMatrix() + { + var sparseMatrix = new SparseMatrix(3); + sparseMatrix[0, 0] = -1; + sparseMatrix[0, 1] = 5; + sparseMatrix[0, 2] = 6; + sparseMatrix[1, 0] = 3; + sparseMatrix[1, 1] = -6; + sparseMatrix[1, 2] = 1; + sparseMatrix[2, 0] = 6; + sparseMatrix[2, 1] = 8; + sparseMatrix[2, 2] = 9; + var ilu = new IncompleteLU(); + ilu.Initialize(sparseMatrix); + var original = GetLowerTriangle(ilu).Multiply(GetUpperTriangle(ilu)); + + for (var i = 0; i < sparseMatrix.RowCount; i++) + { + for (var j = 0; j < sparseMatrix.ColumnCount; j++) + { + Assert.IsTrue(sparseMatrix[i, j].AlmostEqual(original[i, j], -Epsilon.Magnitude()), "#01-" + i + "-" + j); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/PreConditionerTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/PreConditionerTest.cs new file mode 100644 index 00000000..26ff20be --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/PreConditionerTest.cs @@ -0,0 +1,126 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Preconditioners +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Preconditioners; + using MbUnit.Framework; + + public abstract class PreconditionerTest + { + protected const double Epsilon = 1e-10; + + internal SparseMatrix CreateUnitMatrix(int size) + { + var matrix = new SparseMatrix(size); + for (var i = 0; i < size; i++) + { + matrix[i, i] = 2; + } + + return matrix; + } + + protected Vector CreateStandardBcVector(int size) + { + Vector vector = new DenseVector(size); + for (var i = 0; i < size; i++) + { + vector[i] = i + 1; + } + + return vector; + } + + internal abstract IPreConditioner CreatePreconditioner(); + + protected abstract void CheckResult(IPreConditioner preconditioner, SparseMatrix matrix, Vector vector, Vector result); + + [Test] + [MultipleAsserts] + public void ApproximateWithUnitMatrixReturningNewVector() + { + const int Size = 10; + + var newMatrix = CreateUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + + var preconditioner = CreatePreconditioner(); + preconditioner.Initialize(newMatrix); + + var result = preconditioner.Approximate(vector); + + CheckResult(preconditioner, newMatrix, vector, result); + } + + [Test] + [MultipleAsserts] + public void ApproximateReturningOldVector() + { + const int Size = 10; + var newMatrix = CreateUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + + var preconditioner = CreatePreconditioner(); + preconditioner.Initialize(newMatrix); + + Vector result = new DenseVector(vector.Count); + preconditioner.Approximate(vector, result); + + CheckResult(preconditioner, newMatrix, vector, result); + } + + [Test] + [ExpectedArgumentException] + public void ApproximateWithVectorWithIncorrectLength() + { + const int Size = 10; + var newMatrix = CreateUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + + var preconditioner = CreatePreconditioner(); + preconditioner.Initialize(newMatrix); + + Vector result = new DenseVector(vector.Count + 10); + preconditioner.Approximate(vector, result); + } + + [Test] + [ExpectedArgumentNullException] + public void ApproximateWithNullVector() + { + const int Size = 10; + var newMatrix = CreateUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + + var preconditioner = CreatePreconditioner(); + preconditioner.Initialize(newMatrix); + + Vector result = new DenseVector(vector.Count + 10); + preconditioner.Approximate(null, result); + } + + [Test] + [ExpectedArgumentNullException] + public void ApproximateWithNullResultVector() + { + const int Size = 10; + var newMatrix = CreateUnitMatrix(Size); + var vector = CreateStandardBcVector(Size); + + var preconditioner = CreatePreconditioner(); + preconditioner.Initialize(newMatrix); + + Vector result = null; + preconditioner.Approximate(vector, result); + } + + [Test] + [ExpectedArgumentException] + public void ApproximateWithNonInitializedPreconditioner() + { + const int Size = 10; + var vector = CreateStandardBcVector(Size); + var preconditioner = CreatePreconditioner(); + preconditioner.Approximate(vector); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/UnitPreconditionerTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/UnitPreconditionerTest.cs new file mode 100644 index 00000000..28eec73c --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/Preconditioners/UnitPreconditionerTest.cs @@ -0,0 +1,27 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.Preconditioners +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Preconditioners; + using MbUnit.Framework; + + [TestFixture] + public sealed class UnitPreconditionerTest : PreconditionerTest + { + internal override IPreConditioner CreatePreconditioner() + { + return new UnitPreconditioner(); + } + + protected override void CheckResult(IPreConditioner preconditioner, SparseMatrix matrix, Vector vector, Vector result) + { + Assert.AreEqual(typeof(UnitPreconditioner), preconditioner.GetType(), "#01"); + + // Unit preconditioner is doing nothing. Vector and result should be equal + + for (var i = 0; i < vector.Count; i++) + { + Assert.IsTrue(vector[i] == result[i], "#02-" + i); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/DivergenceStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/DivergenceStopCriteriumTest.cs new file mode 100644 index 00000000..23df392f --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/DivergenceStopCriteriumTest.cs @@ -0,0 +1,233 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.StopCriterium +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public sealed class DivergenceStopCriteriumTest + { + [Test] + [ExpectedArgumentOutOfRangeException] + public void CreateWithNegativeMaximumIncrease() + { + new DivergenceStopCriterium(-0.1); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void CreateWithIllegalMinimumIterations() + { + new DivergenceStopCriterium(2); + } + + [Test] + [MultipleAsserts] + public void Create() + { + var criterium = new DivergenceStopCriterium(0.1, 3); + Assert.IsNotNull(criterium, "There should be a criterium"); + + Assert.AreEqual(0.1, criterium.MaximumRelativeIncrease, "Incorrect maximum"); + Assert.AreEqual(3, criterium.MinimumNumberOfIterations, "Incorrect iteration count"); + } + + [Test] + [MultipleAsserts] + public void ResetMaximumIncrease() + { + var criterium = new DivergenceStopCriterium(0.5, 3); + Assert.IsNotNull(criterium, "There should be a criterium"); + + Assert.AreEqual(0.5, criterium.MaximumRelativeIncrease, "Incorrect maximum"); + + criterium.ResetMaximumRelativeIncreaseToDefault(); + Assert.AreEqual(DivergenceStopCriterium.DefaultMaximumRelativeIncrease, criterium.MaximumRelativeIncrease, "Incorrect value"); + } + + [Test] + [MultipleAsserts] + public void ResetMinimumIterationsBelowMaximum() + { + var criterium = new DivergenceStopCriterium(0.5, 15); + Assert.IsNotNull(criterium, "There should be a criterium"); + + Assert.AreEqual(15, criterium.MinimumNumberOfIterations, "Incorrect iteration count"); + + criterium.ResetNumberOfIterationsToDefault(); + Assert.AreEqual(DivergenceStopCriterium.DefaultMinimumNumberOfIterations, criterium.MinimumNumberOfIterations, "Incorrect value"); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void DetermineStatusWithIllegalIterationNumber() + { + var criterium = new DivergenceStopCriterium(0.5, 15); + criterium.DetermineStatus(-1, + new DenseVector(3, 4), + new DenseVector(3, 5), + new DenseVector(3, 6)); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullResidualVector() + { + var criterium = new DivergenceStopCriterium(0.5, 15); + criterium.DetermineStatus(1, + new DenseVector(3, 4), + new DenseVector(3, 5), + null); + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithTooFewIterations() + { + const double Increase = 0.5; + const int Iterations = 10; + + var criterium = new DivergenceStopCriterium(Increase, Iterations); + + // Add residuals. We should not diverge because we'll have to few iterations + for (var i = 0; i < Iterations - 1; i++) + { + criterium.DetermineStatus(i, + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { (i + 1) * (Increase + 0.1) })); + + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail."); + } + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithNoDivergence() + { + const double Increase = 0.5; + const int Iterations = 10; + + var criterium = new DivergenceStopCriterium(Increase, Iterations); + + // Add residuals. We should not diverge because we won't have enough increase + for (var i = 0; i < Iterations * 2; i++) + { + criterium.DetermineStatus(i, + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { (i + 1) * (Increase - 0.01) })); + + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail."); + } + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithDivergenceThroughNaN() + { + const double Increase = 0.5; + const int Iterations = 10; + + var criterium = new DivergenceStopCriterium(Increase, Iterations); + + // Add residuals. We should not diverge because we'll have to few iterations + for (var i = 0; i < Iterations - 5; i++) + { + criterium.DetermineStatus(i, + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { (i + 1) * (Increase - 0.01) })); + + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail."); + } + + // Now make it fail by throwing in a NaN + criterium.DetermineStatus(Iterations, + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { double.NaN })); + + Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Status check fail."); + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithDivergence() + { + const double Increase = 0.5; + const int Iterations = 10; + + var criterium = new DivergenceStopCriterium(Increase, Iterations); + + // Add residuals. We should not diverge because we'll have one to few iterations + double previous = 1; + for (var i = 0; i < Iterations - 1; i++) + { + previous *= (1 + Increase + 0.01); + criterium.DetermineStatus(i, + new DenseVector(new[] { 1.0 }), + new DenseVector(new[] { 1.0 }), + new DenseVector(new[] { previous })); + + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail."); + } + + // Add the final residual. Now we should have divergence + previous *= (1 + Increase + 0.01); + criterium.DetermineStatus(Iterations - 1, + new DenseVector(new[] { 1.0 }), + new DenseVector(new[] { 1.0 }), + new DenseVector(new[] { previous })); + + Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Status check fail."); + } + + [Test] + [MultipleAsserts] + public void ResetCalculationState() + { + const double Increase = 0.5; + const int Iterations = 10; + + var criterium = new DivergenceStopCriterium(Increase, Iterations); + + // Add residuals. Blow it up instantly + criterium.DetermineStatus(1, + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { 1.0 }), + new DenseVector(new [] { double.NaN })); + + Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Status check fail."); + + // Reset the state + criterium.ResetToPrecalculationState(); + + Assert.AreEqual(Increase, criterium.MaximumRelativeIncrease, "Incorrect maximum"); + Assert.AreEqual(Iterations, criterium.MinimumNumberOfIterations, "Incorrect iteration count"); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), criterium.Status, "Status check fail."); + } + + [Test] + [MultipleAsserts] + public void Clone() + { + const double Increase = 0.5; + const int Iterations = 10; + + var criterium = new DivergenceStopCriterium(Increase, Iterations); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var clone = criterium.Clone(); + Assert.IsInstanceOfType(typeof(DivergenceStopCriterium), clone, "Wrong criterium type"); + + var clonedCriterium = clone as DivergenceStopCriterium; + Assert.IsNotNull(clonedCriterium); + // ReSharper disable PossibleNullReferenceException + Assert.AreEqual(criterium.MaximumRelativeIncrease, clonedCriterium.MaximumRelativeIncrease, "Incorrect maximum"); + Assert.AreEqual(criterium.MinimumNumberOfIterations, clonedCriterium.MinimumNumberOfIterations, "Incorrect iteration count"); + // ReSharper restore PossibleNullReferenceException + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/FailureStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/FailureStopCriteriumTest.cs new file mode 100644 index 00000000..81c790fb --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/FailureStopCriteriumTest.cs @@ -0,0 +1,133 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.StopCriterium +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public sealed class FailureStopCriteriumTest + { + [Test] + [MultipleAsserts] + public void Create() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "Should have a criterium now"); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void DetermineStatusWithIllegalIterationNumber() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(-1, new DenseVector(3, 4), new DenseVector(3, 5), new DenseVector(3, 6)); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullSolutionVector() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, null, new DenseVector(3, 6), new DenseVector(4, 4)); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullResidualVector() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, new DenseVector(3, 4), new DenseVector(3, 6), null); + } + + [Test] + [ExpectedArgumentException] + public void DetermineStatusWithNonMatchingVectors() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, new DenseVector(3, 4), new DenseVector(3, 6), new DenseVector(4, 4)); + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithResidualNaN() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new [] { 1.0, 1.0, 2.0 }); + var source = new DenseVector(new [] { 1001.0, 0, 2003.0 }); + var residual = new DenseVector(new [] { 1000, double.NaN, 2001 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationFailure), criterium.Status, "Should be failed"); + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithSolutionNaN() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new[] { 1.0, 1.0, double.NaN }); + var source = new DenseVector(new[] { 1001.0, 0.0, 2003.0 }); + var residual = new DenseVector(new[] { 1000.0, 1000.0, 2001.0 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationFailure), criterium.Status, "Should be failed"); + } + + [Test] + [MultipleAsserts] + public void DetermineStatus() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new[] { 3.0, 2.0, 1.0 }); + var source = new DenseVector(new[] { 1001.0, 0.0, 2003.0 }); + var residual = new DenseVector(new[] { 1.0, 2.0, 3.0 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should be running"); + } + + [Test] + [MultipleAsserts] + public void ResetCalculationState() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new[] { 1.0, 1.0, 2.0 }); + var source = new DenseVector(new[] { 1001.0, 0.0, 2003.0 }); + var residual = new DenseVector(new[] { 1000.0, 1000.0, 2001.0 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should be running"); + + criterium.ResetToPrecalculationState(); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), criterium.Status, "Should not have started"); + } + + [Test] + [MultipleAsserts] + public void Clone() + { + var criterium = new FailureStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var clone = criterium.Clone(); + Assert.IsInstanceOfType(typeof(FailureStopCriterium), clone, "Wrong criterium type"); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/IterationCountStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/IterationCountStopCriteriumTest.cs new file mode 100644 index 00000000..ae95decc --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/IterationCountStopCriteriumTest.cs @@ -0,0 +1,96 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.StopCriterium +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public sealed class IterationCountStopCriteriumTest + { + [Test] + [ExpectedArgumentOutOfRangeException] + public void CreateWithIllegalMinimumIterations() + { + new IterationCountStopCriterium(-1); + } + + [Test] + [MultipleAsserts] + public void Create() + { + var criterium = new IterationCountStopCriterium(10); + Assert.IsNotNull(criterium, "A criterium should have been created"); + } + + [Test] + [MultipleAsserts] + public void ResetMaximumIterations() + { + var criterium = new IterationCountStopCriterium(10); + Assert.IsNotNull(criterium, "A criterium should have been created"); + Assert.AreEqual(10, criterium.MaximumNumberOfIterations, "Incorrect maximum number of iterations"); + + criterium.ResetMaximumNumberOfIterationsToDefault(); + Assert.AreNotEqual(10, criterium.MaximumNumberOfIterations, "Should have reset"); + Assert.AreEqual(IterationCountStopCriterium.DefaultMaximumNumberOfIterations, criterium.MaximumNumberOfIterations, "Reset to the wrong value"); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void DetermineStatusWithIllegalIterationNumber() + { + var criterium = new IterationCountStopCriterium(10); + Assert.IsNotNull(criterium, "A criterium should have been created"); + + criterium.DetermineStatus(-1, new DenseVector(3, 1), new DenseVector(3, 2), new DenseVector(3,3)); + Assert.Fail(); + } + + [Test] + [MultipleAsserts] + public void DetermineStatus() + { + var criterium = new IterationCountStopCriterium(10); + Assert.IsNotNull(criterium, "A criterium should have been created"); + + criterium.DetermineStatus(5, new DenseVector(3, 1), new DenseVector(3, 2), new DenseVector(3, 3)); + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should be running"); + + criterium.DetermineStatus(10, new DenseVector(3, 1), new DenseVector(3, 2), new DenseVector(3, 3)); + Assert.IsInstanceOfType(typeof(CalculationStoppedWithoutConvergence), criterium.Status, "Should be finished"); + } + + [Test] + [MultipleAsserts] + public void ResetCalculationState() + { + var criterium = new IterationCountStopCriterium(10); + Assert.IsNotNull(criterium, "A criterium should have been created"); + + criterium.DetermineStatus(5, new DenseVector(3, 1), new DenseVector(3, 2), new DenseVector(3, 3)); + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should be running"); + + criterium.ResetToPrecalculationState(); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), criterium.Status, "Should not have started"); + } + + [Test] + [MultipleAsserts] + public void Clone() + { + var criterium = new IterationCountStopCriterium(10); + Assert.IsNotNull(criterium, "A criterium should have been created"); + Assert.AreEqual(10, criterium.MaximumNumberOfIterations, "Incorrect maximum"); + + var clone = criterium.Clone(); + Assert.IsInstanceOfType(typeof(IterationCountStopCriterium), clone, "Wrong criterium type"); + + var clonedCriterium = clone as IterationCountStopCriterium; + Assert.IsNotNull(clonedCriterium); + // ReSharper disable PossibleNullReferenceException + Assert.AreEqual(criterium.MaximumNumberOfIterations, clonedCriterium.MaximumNumberOfIterations, "Clone failed"); + // ReSharper restore PossibleNullReferenceException + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/ResidualStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/ResidualStopCriteriumTest.cs new file mode 100644 index 00000000..51e8d893 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Solvers/StopCriterium/ResidualStopCriteriumTest.cs @@ -0,0 +1,261 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Solvers.StopCriterium +{ + using LinearAlgebra.Double; + using LinearAlgebra.Double.Solvers.Status; + using LinearAlgebra.Double.Solvers.StopCriterium; + using MbUnit.Framework; + + [TestFixture] + public sealed class ResidualStopCriteriumTest + { + [Test] + [ExpectedArgumentOutOfRangeException] + public void CreateWithNegativeMaximum() + { + new ResidualStopCriterium(-0.1); + Assert.Fail(); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void CreateWithIllegalMinimumIterations() + { + new ResidualStopCriterium(-1); + Assert.Fail(); + } + + [Test] + [MultipleAsserts] + public void Create() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + Assert.AreEqual(1e-8, criterium.Maximum, "Incorrect maximum"); + Assert.AreEqual(50, criterium.MinimumIterationsBelowMaximum, "Incorrect iteration count"); + } + + [Test] + [MultipleAsserts] + public void ResetMaximum() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.ResetMaximumResidualToDefault(); + Assert.AreEqual(ResidualStopCriterium.DefaultMaximumResidual, criterium.Maximum, "Incorrect maximum"); + } + + [Test] + [MultipleAsserts] + public void ResetMinimumIterationsBelowMaximum() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.ResetMinimumIterationsBelowMaximumToDefault(); + Assert.AreEqual(ResidualStopCriterium.DefaultMinimumIterationsBelowMaximum, criterium.MinimumIterationsBelowMaximum, "Incorrect iteration count"); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void DetermineStatusWithIllegalIterationNumber() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(-1, + new DenseVector(3, 4), + new DenseVector(3, 5), + new DenseVector(3, 6)); + Assert.Fail(); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullSolutionVector() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, + null, + new DenseVector(3, 5), + new DenseVector(3, 6)); + Assert.Fail(); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullSourceVector() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, + new DenseVector(3, 4), + null, + new DenseVector(3, 6)); + Assert.Fail(); + } + + [Test] + [ExpectedArgumentNullException] + public void DetermineStatusWithNullResidualVector() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, + new DenseVector(3, 4), + new DenseVector(3, 5), + null); + } + + [Test] + [ExpectedArgumentException] + public void DetermineStatusWithNonMatchingSolutionVector() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, + new DenseVector(4, 4), + new DenseVector(3, 4), + new DenseVector(3, 4)); + } + + [Test] + [ExpectedArgumentException] + public void DetermineStatusWithNonMatchingSourceVector() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, + new DenseVector(3, 4), + new DenseVector(4, 4), + new DenseVector(3, 4)); + } + + [Test] + [ExpectedArgumentException] + public void DetermineStatusWithNonMatchingResidualVector() + { + var criterium = new ResidualStopCriterium(1e-8, 50); + Assert.IsNotNull(criterium, "There should be a criterium"); + + criterium.DetermineStatus(1, + new DenseVector(3, 4), + new DenseVector(3, 4), + new DenseVector(4, 4)); + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithSourceNaN() + { + var criterium = new ResidualStopCriterium(1e-3, 10); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new[] { 1.0, 1.0, 2.0 }); + var source = new DenseVector(new[] { 1.0, 1.0, double.NaN }); + var residual = new DenseVector(new[] { 1000.0, 1000.0, 2001.0 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Should be diverged"); + } + + [Test] + [MultipleAsserts] + public void DetermineStatusWithResidualNaN() + { + var criterium = new ResidualStopCriterium(1e-3, 10); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new[] { 1.0, 1.0, 2.0 }); + var source = new DenseVector(new[] { 1.0, 1.0, 2.0 }); + var residual = new DenseVector(new[] { 1000.0, double.NaN, 2001.0 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Should be diverged"); + } + + // Bugfix: The unit tests for the BiCgStab solver run with a super simple matrix equation + // which converges at the first iteration. The default settings for the + // residual stop criterium should be able to handle this. + [Test] + [MultipleAsserts] + public void DetermineStatusWithConvergenceAtFirstIteration() + { + var criterium = new ResidualStopCriterium(); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new[] { 1.0, 1.0, 1.0 }); + var source = new DenseVector(new[] { 1.0, 1.0, 1.0 }); + var residual = new DenseVector(new[] { 0.0, 0.0, 0.0 }); + + criterium.DetermineStatus(0, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationConverged), criterium.Status, "Should be done"); + } + + [Test] + [MultipleAsserts] + public void DetermineStatus() + { + var criterium = new ResidualStopCriterium(1e-3, 10); + Assert.IsNotNull(criterium, "There should be a criterium"); + + // Note that the solution vector isn't actually being used so ... + var solution = new DenseVector(new[] { double.NaN, double.NaN, double.NaN }); + + // Set the source values + var source = new DenseVector(new[] { 1.000, 1.000, 2.001 }); + + // Set the residual values + var residual = new DenseVector(new[] { 0.001, 0.001, 0.002 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should still be running"); + + criterium.DetermineStatus(16, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationConverged), criterium.Status, "Should be done"); + } + + [Test] + [MultipleAsserts] + public void ResetCalculationState() + { + var criterium = new ResidualStopCriterium(1e-3, 10); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var solution = new DenseVector(new[] { 0.001, 0.001, 0.002 }); + var source = new DenseVector(new[] { 0.001, 0.001, 0.002 }); + var residual = new DenseVector(new[] { 1.000, 1.000, 2.001 }); + + criterium.DetermineStatus(5, solution, source, residual); + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should be running"); + + criterium.ResetToPrecalculationState(); + Assert.IsInstanceOfType(typeof(CalculationIndetermined), criterium.Status, "Should not have started"); + } + + [Test] + [MultipleAsserts] + public void Clone() + { + var criterium = new ResidualStopCriterium(1e-3, 10); + Assert.IsNotNull(criterium, "There should be a criterium"); + + var clone = criterium.Clone(); + Assert.IsInstanceOfType(typeof(ResidualStopCriterium), clone, "Wrong criterium type"); + + var clonedCriterium = clone as ResidualStopCriterium; + Assert.IsNotNull(clonedCriterium); + // ReSharper disable PossibleNullReferenceException + Assert.AreEqual(criterium.Maximum, clonedCriterium.Maximum, "Clone failed"); + Assert.AreEqual(criterium.MinimumIterationsBelowMaximum, clonedCriterium.MinimumIterationsBelowMaximum, "Clone failed"); + // ReSharper restore PossibleNullReferenceException + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/SparseMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Double/SparseMatrixTests.cs index 6c212960..3c8e37a9 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/SparseMatrixTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/SparseMatrixTests.cs @@ -70,7 +70,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double foreach (var name in testData.Keys) { - Assert.AreEqual(testMatrices[name], testData[name]); + Assert.AreEqual(TestMatrices[name], testData[name]); } } @@ -87,9 +87,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Test] public void MatrixFrom2DArrayIsCopy() { - var matrix = new SparseMatrix(testData2D["Singular3x3"]); + var matrix = new SparseMatrix(TestData2D["Singular3x3"]); matrix[0, 0] = 10.0; - Assert.AreEqual(1.0, testData2D["Singular3x3"][0, 0]); + Assert.AreEqual(1.0, TestData2D["Singular3x3"][0, 0]); } [Test] @@ -101,12 +101,12 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [Row("Wide2x3")] public void CanCreateMatrixFrom2DArray(string name) { - var matrix = new SparseMatrix(testData2D[name]); - for (var i = 0; i < testData2D[name].GetLength(0); i++) + var matrix = new SparseMatrix(TestData2D[name]); + for (var i = 0; i < TestData2D[name].GetLength(0); i++) { - for (var j = 0; j < testData2D[name].GetLength(1); j++) + for (var j = 0; j < TestData2D[name].GetLength(1); j++) { - Assert.AreEqual(testData2D[name][i, j], matrix[i, j]); + Assert.AreEqual(TestData2D[name][i, j], matrix[i, j]); } } } @@ -143,7 +143,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double [ExpectedArgumentException] public void IdentityFailsWithZeroOrNegativeOrder(int order) { - var matrix = SparseMatrix.Identity(order); + SparseMatrix.Identity(order); } [Test] diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 5256c720..904d3373 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -90,6 +90,8 @@ + + @@ -99,6 +101,21 @@ + + + + + + + + + + + + + + +