diff --git a/src/Numerics/LinearAlgebra/Complex/DenseMatrix.cs b/src/Numerics/LinearAlgebra/Complex/DenseMatrix.cs new file mode 100644 index 00000000..5367bf76 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/DenseMatrix.cs @@ -0,0 +1,742 @@ +// +// 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.Complex +{ + using System; + using System.Numerics; + using Distributions; + using Generic; + using Properties; + using Threading; + + /// + /// A Matrix class with dense storage. The underlying storage is a one dimensional array in column-major order. + /// + public class DenseMatrix : 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 DenseMatrix(int order) + : base(order) + { + Data = new Complex[order * order]; + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + public DenseMatrix(int rows, int columns) + : base(rows, columns) + { + Data = new Complex[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 DenseMatrix(int rows, int columns, Complex value) + : base(rows, columns) + { + Data = new Complex[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. This constructor + /// will reference the one dimensional array and not copy it. + /// + /// The number of rows. + /// The number of columns. + /// The one dimensional array to create this matrix from. This array should store the matrix in column-major order. + public DenseMatrix(int rows, int columns, Complex[] array) + : base(rows, columns) + { + Data = array; + } + + /// + /// Initializes a new instance of the class from a 2D array. This constructor + /// will allocate a completely new memory block for storing the dense matrix. + /// + /// The 2D array to create this matrix from. + public DenseMatrix(Complex[,] array) + : base(array.GetLength(0), array.GetLength(1)) + { + var rows = array.GetLength(0); + var columns = array.GetLength(1); + Data = new Complex[rows * columns]; + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < columns; j++) + { + Data[(j * rows) + i] = array[i, j]; + } + } + } + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + internal Complex[] Data + { + get; + private set; + } + + /// + /// Creates a DenseMatrix for the given number of rows and columns. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + /// + /// A DenseMatrix with the given dimensions. + /// + public override Matrix CreateMatrix(int numberOfRows, int numberOfColumns) + { + return new DenseMatrix(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 DenseVector(size); + } + + /// + /// Retrieves the requested element without range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The requested element. + /// + public override Complex At(int row, int column) + { + return Data[(column * RowCount) + row]; + } + + /// + /// Sets the value of the given element. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The value to set the element to. + /// + public override void At(int row, int column, Complex value) + { + Data[(column * RowCount) + row] = value; + } + + /// + /// Sets all values to zero. + /// + public override void Clear() + { + Array.Clear(Data, 0, Data.Length); + } + + /// + /// Returns the transpose of this matrix. + /// + /// The transpose of this matrix. + public override Matrix Transpose() + { + var ret = new DenseMatrix(ColumnCount, RowCount); + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = 0; i < RowCount; i++) + { + ret.Data[(i * ColumnCount) + j] = Data[index + i]; + } + } + + return ret; + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public override Matrix ConjugateTranspose() + { + var ret = new DenseMatrix(ColumnCount, RowCount); + for (var j = 0; j < ColumnCount; j++) + { + var index = j * RowCount; + for (var i = 0; i < RowCount; i++) + { + ret.Data[(i * ColumnCount) + j] = Data[index + i].Conjugate(); + } + } + + 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 += Data[(j * RowCount) + i].Magnitude; + } + + 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 += aat.Data[(i * RowCount) + i].Magnitude; + } + + 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 += Data[(j * RowCount) + i].Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + #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. + public override void Add(Matrix other) + { + var m = other as DenseMatrix; + if (m == null) + { + base.Add(other); + } + else + { + 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(DenseMatrix 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. + public override void Subtract(Matrix other) + { + var m = other as DenseMatrix; + if (m == null) + { + base.Subtract(other); + } + else + { + 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(DenseMatrix 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); + } + + /// + /// Multiplies each element of this matrix with a complex. + /// + /// The complex to multiply with. + public override void Multiply(Complex complex) + { + Control.LinearAlgebraProvider.ScaleArray(complex, Data); + } + + /// + /// Multiplies this dense matrix with 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 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 DenseMatrix; + var r = result as DenseMatrix; + + if (m == null || r == null) + { + base.Multiply(other, result); + } + else + { + Control.LinearAlgebraProvider.MatrixMultiply( + Data, + RowCount, + ColumnCount, + m.Data, + m.RowCount, + m.ColumnCount, + 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 DenseMatrix; + if (m == null) + { + return base.Multiply(other); + } + + var result = (DenseMatrix)CreateMatrix(RowCount, other.ColumnCount); + Multiply(other, result); + 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. + /// + /// 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 DenseMatrix operator *(DenseMatrix leftSide, DenseMatrix 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 (DenseMatrix)leftSide.Multiply(rightSide); + } + + #endregion + + #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 dense identity matrix. + /// + /// If is less than one. + /// + public static DenseMatrix Identity(int order) + { + var m = new DenseMatrix(order); + for (var i = 0; i < order; i++) + { + m[i, i] = Complex.One; + } + + return m; + } + + #endregion + + /// + /// Negate each element of this matrix. + /// + /// If the result matrix is . + /// if the result matrix's dimensions are not the same as this matrix. + public override void Negate() + { + Multiply(-1); + } + + /// + /// Generates matrix with random elements. + /// + /// Number of rows. + /// Number of columns. + /// Continuous Random Distribution or Source + /// + /// An numberOfRows-by-numberOfColumns matrix with elements distributed according to the provided distribution. + /// + /// If the parameter is not positive. + /// If the parameter is not positive. + public override Matrix Random(int numberOfRows, int numberOfColumns, IContinuousDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfColumns"); + } + + var matrix = CreateMatrix(numberOfRows, numberOfColumns); + CommonParallel.For( + 0, + ColumnCount, + j => + { + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, j] = new Complex(distribution.Sample(), distribution.Sample()); + } + }); + + return matrix; + } + + /// + /// Generates matrix with random elements. + /// + /// Number of rows. + /// Number of columns. + /// Continuous Random Distribution or Source + /// + /// An numberOfRows-by-numberOfColumns matrix with elements distributed according to the provided distribution. + /// + /// If the parameter is not positive. + /// If the parameter is not positive. + public override Matrix Random(int numberOfRows, int numberOfColumns, IDiscreteDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfColumns"); + } + + var matrix = CreateMatrix(numberOfRows, numberOfColumns); + CommonParallel.For( + 0, + ColumnCount, + j => + { + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, j] = new Complex(distribution.Sample(), distribution.Sample()); + } + }); + + return matrix; + } + + #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + /// + /// Subtract two values T-T + /// + /// Left operand value + /// Right operand value + /// Result of subtract + protected sealed override Complex SubtractT(Complex val1, Complex val2) + { + return val1 - val2; + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Divide two values T/T + /// + /// Left operand value + /// Right operand value + /// Result of divide + protected sealed override Complex DivideT(Complex val1, Complex val2) + { + return val1 / val2; + } + + /// + /// Is equal to one? + /// + /// Value to check + /// True if one; otherwise false + protected sealed override bool IsOneT(Complex val1) + { + return val1.AlmostEqual(Complex.One); + } + + /// + /// Take absolute value + /// + /// Source alue + /// True if one; otherwise false + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/DenseVector.cs b/src/Numerics/LinearAlgebra/Complex/DenseVector.cs new file mode 100644 index 00000000..52f9c5ba --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/DenseVector.cs @@ -0,0 +1,1513 @@ +// +// 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.Complex +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using Distributions; + using Generic; + using NumberTheory; + using Properties; + using Threading; + + /// + /// A vector using dense storage. + /// + public class DenseVector : Vector + { + /// + /// Initializes a new instance of the class with a given size. + /// + /// + /// the size of the vector. + /// + /// + /// If is less than one. + /// + public DenseVector(int size) : base(size) + { + Data = new Complex[size]; + } + + /// + /// Initializes a new instance of the class with a given size + /// and each element set to the given value; + /// + /// + /// the size of the vector. + /// + /// + /// the value to set each element to. + /// + /// + /// If is less than one. + /// + public DenseVector(int size, Complex value) : this(size) + { + for (var index = 0; index < Data.Length; index++) + { + Data[index] = value; + } + } + + /// + /// Initializes a new instance of the class by + /// copying the values from another. + /// + /// + /// The vector to create the new vector from. + /// + public DenseVector(Vector other) : this(other.Count) + { + CommonParallel.For( + 0, + Data.Length, + index => this[index] = other[index]); + } + + /// + /// Initializes a new instance of the class by + /// copying the values from another. + /// + /// + /// The vector to create the new vector from. + /// + public DenseVector(DenseVector other) : this(other.Count) + { + CommonParallel.For( + 0, + Data.Length, + index => Data[index] = other.Data[index]); + } + + /// + /// Initializes a new instance of the class for an array. + /// + /// The array to create this vector from. + /// The vector does not copy the array, but keeps a reference to it. Any + /// changes to the vector will also change the array. + public DenseVector(Complex[] array) : base(array.Length) + { + Data = array; + } + + /// + /// Gets the vector's internal data. + /// + /// The vector's internal data. + /// Changing values in the array also changes the corresponding value in vector. Use with care. + internal Complex[] Data + { + get; + private set; + } + + /// + /// Returns a reference to the internal data structure. + /// + /// The DenseVector whose internal data we are + /// returning. + /// + /// A reference to the internal date of the given vector. + /// + public static implicit operator Complex[](DenseVector vector) + { + if (vector == null) + { + throw new ArgumentNullException(); + } + + return vector.Data; + } + + /// + /// Returns a vector bound directly to a reference of the provided array. + /// + /// The array to bind to the DenseVector object. + /// + /// A DenseVector whose values are bound to the given array. + /// + public static implicit operator DenseVector(Complex[] array) + { + if (array == null) + { + throw new ArgumentNullException(); + } + + return new DenseVector(array); + } + + /// + /// Create a matrix based on this vector in column form (one single column). + /// + /// This vector as a column matrix. + public override Matrix ToColumnMatrix() + { + var matrix = new DenseMatrix(Count, 1); + for (var i = 0; i < Data.Length; i++) + { + matrix[i, 0] = Data[i]; + } + + return matrix; + } + + /// + /// Create a matrix based on this vector in row form (one single row). + /// + /// This vector as a row matrix. + public override Matrix ToRowMatrix() + { + var matrix = new DenseMatrix(1, Count); + for (var i = 0; i < Data.Length; i++) + { + matrix[0, i] = Data[i]; + } + + return matrix; + } + + /// Gets or sets the value at the given . + /// The index of the value to get or set. + /// The value of the vector at the given . + /// If is negative or + /// greater than the size of the vector. + public override Complex this[int index] + { + get + { + return Data[index]; + } + + set + { + Data[index] = value; + } + } + + /// + /// Creates a matrix with the given dimensions using the same storage type + /// as this vector. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + /// + /// A matrix with the given dimensions. + /// + public override Matrix CreateMatrix(int rows, int columns) + { + return new DenseMatrix(rows, columns); + } + + /// + /// Creates a Vector of the given size using the same storage type + /// as this vector. + /// + /// + /// The size of the Vector to create. + /// + /// + /// The new Vector. + /// + public override Vector CreateVector(int size) + { + return new DenseVector(size); + } + + /// + /// Adds a complex to each element of the vector. + /// + /// The complex to add. + /// A copy of the vector with the complex added. + public override Vector Add(Complex complex) + { + if (complex == Complex.Zero) + { + return Clone(); + } + + var copy = (DenseVector)Clone(); + CommonParallel.For( + 0, + Data.Length, + index => copy.Data[index] += complex); + return copy; + } + + /// + /// Adds a complex to each element of the vector and stores the result in the result vector. + /// + /// The complex to add. + /// The vector to store the result of the addition. + /// If the result vector is . + /// If this vector and are not the same size. + public override void Add(Complex complex, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + var dense = result as DenseVector; + if (dense == null) + { + base.Add(complex, result); + } + else + { + CommonParallel.For( + 0, + Data.Length, + index => dense.Data[index] = Data[index] + complex); + } + } + + /// + /// Adds another vector to this vector. + /// + /// The vector to add to this one. + /// A new vector containing the sum of both vectors. + /// If the other vector is . + /// If this vector and are not the same size. + public override Vector Add(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var denseVector = other as DenseVector; + + if (denseVector == null) + { + return base.Add(other); + } + + var copy = (DenseVector)Clone(); + Control.LinearAlgebraProvider.AddVectorToScaledVector(copy.Data, Complex.One, denseVector.Data); + return copy; + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void Add(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Add(other); + tmp.CopyTo(result); + } + else + { + var rdense = result as DenseVector; + var odense = other as DenseVector; + if (rdense != null && odense != null) + { + CopyTo(result); + Control.LinearAlgebraProvider.AddVectorToScaledVector(rdense.Data, Complex.One, odense.Data); + } + else + { + CommonParallel.For( + 0, + Data.Length, + index => result[index] = Data[index] + other[index]); + } + } + } + + /// + /// Returns a Vector containing the same values of . + /// + /// This method is included for completeness. + /// The vector to get the values from. + /// A vector containing a the same values as . + /// If is . + public static Vector operator +(DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return rightSide.Plus(); + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static Vector operator +(DenseVector leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + return leftSide.Add(rightSide); + } + + /// + /// Subtracts a complex from each element of the vector. + /// + /// The complex to subtract. + /// A new vector containing the subtraction of this vector and the complex. + public override Vector Subtract(Complex complex) + { + if (complex == Complex.Zero) + { + return Clone(); + } + + var copy = (DenseVector)Clone(); + CommonParallel.For( + 0, + Data.Length, + index => copy.Data[index] -= complex); + return copy; + } + + /// + /// Subtracts a complex from each element of the vector and stores the result in the result vector. + /// + /// The complex to subtract. + /// The vector to store the result of the subtraction. + /// If the result vector is . + /// If this vector and are not the same size. + public override void Subtract(Complex complex, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + var dense = result as DenseVector; + if (dense == null) + { + base.Subtract(complex, result); + } + else + { + CommonParallel.For( + 0, + Data.Length, + index => dense.Data[index] = Data[index] - complex); + } + } + + /// + /// Subtracts another vector from this vector. + /// + /// The vector to subtract from this one. + /// A new vector containing the subtraction of the the two vectors. + /// If the other vector is . + /// If this vector and are not the same size. + public override Vector Subtract(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var denseVector = other as DenseVector; + + if (denseVector == null) + { + return base.Subtract(other); + } + + var copy = (DenseVector)Clone(); + Control.LinearAlgebraProvider.AddVectorToScaledVector(copy.Data, -Complex.One, denseVector.Data); + return copy; + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void Subtract(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Subtract(other); + tmp.CopyTo(result); + } + else + { + var rdense = result as DenseVector; + var odense = other as DenseVector; + if (rdense != null && odense != null) + { + CopyTo(result); + Control.LinearAlgebraProvider.AddVectorToScaledVector(rdense.Data, -Complex.One, odense.Data); + } + else + { + CommonParallel.For( + 0, + Data.Length, + index => result[index] = Data[index] - other[index]); + } + } + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static Vector operator -(DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static Vector operator -(DenseVector leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + return leftSide.Subtract(rightSide); + } + + /// + /// Returns a negated vector. + /// + /// The negated vector. + /// Added as an alternative to the unary negation operator. + public override Vector Negate() + { + var result = new DenseVector(Count); + CommonParallel.For( + 0, + Data.Length, + index => result[index] = -Data[index]); + + return result; + } + + /// + /// Multiplies a complex to each element of the vector. + /// + /// The complex to multiply. + /// A new vector that is the multiplication of the vector and the complex. + public override Vector Multiply(Complex complex) + { + if (complex == Complex.One) + { + return Clone(); + } + + var copy = (DenseVector)Clone(); + Control.LinearAlgebraProvider.ScaleArray(complex, copy.Data); + return copy; + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector to add. + /// The result of the addition. + /// If is not of the same size. + /// If is . + public override Complex DotProduct(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var denseVector = other as DenseVector; + + if (denseVector == null) + { + return base.DotProduct(other); + } + + return Control.LinearAlgebraProvider.DotProduct(Data, denseVector.Data); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The vector to scale. + /// The Complex value. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(DenseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + return (DenseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The Complex value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static DenseVector operator *(Complex leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return (DenseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static Complex operator *(DenseVector leftSide, DenseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + return Control.LinearAlgebraProvider.DotProduct(leftSide.Data, rightSide.Data); + } + + /// + /// Divides a vector with a complex. + /// + /// The vector to divide. + /// The Complex value. + /// The result of the division. + /// If is . + public static DenseVector operator /(DenseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + return (DenseVector)leftSide.Multiply(Complex.One / rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + var index = 0; + var min = Data[index].Magnitude; + for (var i = 1; i < Count; i++) + { + var test = Data[i].Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return index; + } + + /// + /// Returns the value of the absolute minimum element. + /// + /// The value of the absolute minimum element. + public override double AbsoluteMinimum() + { + return Data[AbsoluteMinimumIndex()].Magnitude; + } + + /// + /// Returns the value of the absolute maximum element. + /// + /// The value of the absolute maximum element. + public override double AbsoluteMaximum() + { + return Data[AbsoluteMaximumIndex()].Magnitude; + } + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int AbsoluteMaximumIndex() + { + var index = 0; + var max = Data[index].Magnitude; + for (var i = 1; i < Count; i++) + { + var test = Data[i].Magnitude; + if (test > max) + { + index = i; + max = test; + } + } + + return index; + } + + /// + /// Creates a vector containing specified elements. + /// + /// The first element to begin copying from. + /// The number of elements to copy. + /// A vector containing a copy of the specified elements. + /// If is not positive or + /// greater than or equal to the size of the vector. + /// If + is greater than or equal to the size of the vector. + /// + /// If is not positive. + public override Vector SubVector(int index, int length) + { + if (index < 0 || index >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (length <= 0) + { + throw new ArgumentOutOfRangeException("length"); + } + + if (index + length > Count) + { + throw new ArgumentOutOfRangeException("length"); + } + + var result = new DenseVector(length); + + CommonParallel.For( + index, + index + length, + i => result.Data[i - index] = Data[i]); + return result; + } + + /// + /// Set the values of this vector to the given values. + /// + /// The array containing the values to use. + /// If is . + /// If is not the same size as this vector. + public override void SetValues(Complex[] values) + { + if (values == null) + { + throw new ArgumentNullException("values"); + } + + if (values.Length != Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "values"); + } + + CommonParallel.For( + 0, + values.Length, + i => Data[i] = values[i]); + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex Sum() + { + var result = Complex.Zero; + for (var i = 0; i < Count; i++) + { + result += Data[i]; + } + + return result; + } + + /// + /// Computes the sum of the absolute value of the vector's elements. + /// + /// The sum of the absolute value of the vector's elements. + public override double SumMagnitudes() + { + double result = 0; + for (var i = 0; i < Count; i++) + { + result += Data[i].Magnitude; + } + + return result; + } + + /// + /// Pointwise multiplies this vector with another vector. + /// + /// The vector to pointwise multiply with this one. + /// A new vector which is the pointwise multiplication of the two vectors. + /// If the other vector is . + /// If this vector and are not the same size. + public override Vector PointwiseMultiply(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var denseVector = other as DenseVector; + + if (denseVector == null) + { + return base.PointwiseMultiply(other); + } + + var copy = (DenseVector)Clone(); + CommonParallel.For( + 0, + Count, + index => copy[index] *= other[index]); + return copy; + } + + /// + /// Pointwise multiplies this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise multiply with this one. + /// The vector to store the result of the pointwise multiplication. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void PointwiseMultiply(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = PointwiseMultiply(other); + tmp.CopyTo(result); + } + else + { + var dense = result as DenseVector; + if (dense == null) + { + base.PointwiseMultiply(other, result); + } + else + { + CommonParallel.For( + 0, + Data.Length, + index => dense.Data[index] = Data[index] * other[index]); + } + } + } + + /// + /// Pointwise divide this vector with another vector. + /// + /// The vector to pointwise divide this one by. + /// A new vector which is the pointwise division of the two vectors. + /// If the other vector is . + /// If this vector and are not the same size. + public override Vector PointwiseDivide(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var denseVector = other as DenseVector; + + if (denseVector == null) + { + return base.PointwiseMultiply(other); + } + + var copy = (DenseVector)Clone(); + CommonParallel.For( + 0, + Count, + index => copy[index] /= other[index]); + return copy; + } + + /// + /// Pointwise divide this vector with another vector and stores the result into the result vector. + /// + /// The vector to pointwise divide this one by. + /// The vector to store the result of the pointwise division. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void PointwiseDivide(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = PointwiseDivide(other); + tmp.CopyTo(result); + } + else + { + var dense = result as DenseVector; + if (dense == null) + { + base.PointwiseDivide(other, result); + } + else + { + CommonParallel.For( + 0, + Data.Length, + index => dense.Data[index] = Data[index] / other[index]); + } + } + } + + /// + /// Outer product of two vectors + /// + /// First vector + /// Second vector + /// Matrix M[i,j] = u[i]*v[j] + /// If the u vector is . + /// If the v vector is . + public static DenseMatrix OuterProduct(DenseVector u, DenseVector v) + { + if (u == null) + { + throw new ArgumentNullException("u"); + } + + if (v == null) + { + throw new ArgumentNullException("v"); + } + + var matrix = new DenseMatrix(u.Count, v.Count); + CommonParallel.For( + 0, + u.Count, + i => + { + for (var j = 0; j < v.Count; j++) + { + matrix.At(i, j, u.Data[i] * v.Data[j]); + } + }); + return matrix; + } + + /// + /// Generates a vector with random elements + /// + /// Number of elements in the vector. + /// Continuous Random Distribution or Source + /// + /// A vector with n-random elements distributed according + /// to the specified random distribution. + /// + /// If the n vector is non positive. + public override Vector Random(int length, IContinuousDistribution randomDistribution) + { + if (length < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "length"); + } + + var v = (DenseVector)CreateVector(length); + for (var index = 0; index < v.Data.Length; index++) + { + v.Data[index] = randomDistribution.Sample(); + } + + return v; + } + + /// + /// Generates a vector with random elements + /// + /// Number of elements in the vector. + /// Continuous Random Distribution or Source + /// + /// A vector with n-random elements distributed according + /// to the specified random distribution. + /// + /// If the n vector is non positive. + public override Vector Random(int length, IDiscreteDistribution randomDistribution) + { + if (length < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "length"); + } + + var v = (DenseVector)CreateVector(length); + for (var index = 0; index < v.Data.Length; index++) + { + v.Data[index] = randomDistribution.Sample(); + } + + return v; + } + + /// + /// Tensor Product (Dyadic) of this and another vector. + /// + /// The vector to operate on. + /// + /// Matrix M[i,j] = this[i] * v[j]. + /// + /// + public Matrix TensorMultiply(DenseVector v) + { + return OuterProduct(this, v); + } + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = (sum(abs(this[i])^p))^(1/p) + public override double Norm(double p) + { + if (p < 0.0) + { + throw new ArgumentOutOfRangeException("p"); + } + + if (1.0 == p) + { + return CommonParallel.Aggregate( + 0, + Count, + index => Data[index].Magnitude); + } + + if (Double.IsPositiveInfinity(p)) + { + return CommonParallel.Select( + 0, + Count, + (index, localData) => Math.Max(localData, Data[index].Magnitude), + Math.Max); + } + + var sum = CommonParallel.Aggregate( + 0, + Count, + index => Math.Pow(Data[index].Magnitude, p)); + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Normalizes this vector to a unit vector with respect to the p-norm. + /// + /// + /// The p value. + /// + /// + /// This vector normalized to a unit vector with respect to the p-norm. + /// + public override Vector Normalize(double p) + { + if (p < 0.0) + { + throw new ArgumentOutOfRangeException("p"); + } + + var norm = Norm(p); + var clone = Clone(); + if (norm == 0.0) + { + return clone; + } + + clone.Multiply(1.0 / norm, clone); + + return clone; + } + + #region Parse Functions + + /// + /// Creates a Complex dense vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n;n;..', '(n;n;..)', '[n;n;...]', where n is a Complex. + /// + /// + /// A Complex dense vector containing the values specified by the given string. + /// + /// + /// The string to parse. + /// + public static DenseVector Parse(string value) + { + return Parse(value, null); + } + + /// + /// Creates a Complex dense vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n;n;..', '(n;n;..)', '[n;n;...]', where n is a double. + /// + /// + /// A Complex dense vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static DenseVector Parse(string value, IFormatProvider formatProvider) + { + if (value == null) + { + throw new ArgumentNullException(value); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // keywords + var textInfo = formatProvider.GetTextInfo(); + var keywords = new[] { textInfo.ListSeparator }; + + // lexing + var tokens = new LinkedList(); + GlobalizationHelper.Tokenize(tokens.AddFirst(value), keywords, 0); + var token = tokens.First; + + if (token == null || tokens.Count.IsEven()) + { + throw new FormatException(); + } + + // parsing + var data = new Complex[(tokens.Count + 1) >> 1]; + for (var i = 0; i < data.Length; i++) + { + if (token == null || token.Value == textInfo.ListSeparator) + { + throw new FormatException(); + } + + data[i] = token.Value.ToComplex(formatProvider); + + token = token.Next; + if (token != null) + { + token = token.Next; + } + } + + return new DenseVector(data); + } + + /// + /// Converts the string representation of a complex dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out DenseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a complex dense vector to double-precision dense vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out DenseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + + #endregion + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int MaximumIndex() + { + throw new NotSupportedException(); + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + throw new NotSupportedException(); + } + + /// + /// Resets all values to zero. + /// + public override void Clear() + { + Array.Clear(Data, 0, Data.Length); + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + public override void Conjugate(Vector target) + { + if (target == null) + { + throw new ArgumentNullException("target"); + } + + if (Count != target.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "target"); + } + + if (ReferenceEquals(this, target)) + { + var tmp = CreateVector(Count); + Conjugate(tmp); + tmp.CopyTo(target); + } + + CommonParallel.For( + 0, + Count, + index => target[index] = this[index].Conjugate()); + } + + #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + /// + /// Subtract two values T-T + /// + /// Left operand value + /// Right operand value + /// Result of subtract + protected sealed override Complex SubtractT(Complex val1, Complex val2) + { + return val1 - val2; + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Divide two values T/T + /// + /// Left operand value + /// Right operand value + /// Result of divide + protected sealed override Complex DivideT(Complex val1, Complex val2) + { + return val1 / val2; + } + + /// + /// Is equal to one? + /// + /// Value to check + /// True if one; otherwise false + protected sealed override bool IsOneT(Complex val1) + { + return Complex.One.AlmostEqual(val1); + } + + /// + /// Take absolute value + /// + /// Source alue + /// True if one; otherwise false + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/DiagonalMatrix.cs b/src/Numerics/LinearAlgebra/Complex/DiagonalMatrix.cs new file mode 100644 index 00000000..0bf51c79 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/DiagonalMatrix.cs @@ -0,0 +1,1718 @@ +// +// 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.Complex +{ + using System; + using System.Linq; + using System.Numerics; + using Distributions; + using Generic; + 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 Complex[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 Complex[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, Complex value) : base(rows, columns) + { + Data = new Complex[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, Complex[] 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(Complex[,] 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].Real != 0.0) && !double.IsNaN(array[i, j].Real)) || ((array[i, j].Imaginary != 0.0) && !double.IsNaN(array[i, j].Imaginary))) + { + throw new IndexOutOfRangeException("Cannot set an off-diagonal element in a diagonal matrix."); + } + } + } + } + + /// + /// Gets the matrix's data. + /// + /// The matrix's data. + internal Complex[] 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 Complex 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, Complex value) + { + if (row == column) + { + Data[row] = value; + } + else if (((value.Real != 0.0) && !double.IsNaN(value.Real)) || ((value.Imaginary != 0.0) && !double.IsNaN(value.Imaginary))) + { + 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].GetHashCode()); +#else + hash ^= BitConverter.DoubleToInt64Bits(Data[i].GetHashCode()); +#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(Complex[] source) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + + if (source.Length != Data.Length) + { + throw new ArgumentException(Resources.ArgumentArraysSameLength, "source"); + } + + CommonParallel.For(0, source.Length, index => Data[index] = source[index]); + } + + /// + /// 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"); + } + + CommonParallel.For(0, denseSource.Data.Length, index => Data[index] = denseSource.Data[index]); + } + + /// + /// Multiplies each element of this matrix with a scalar. + /// + /// The scalar to multiply with. + public override void Multiply(Complex 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 Complex[r.Data.Length]; + var otherDataCopy = new Complex[r.Data.Length]; + + CommonParallel.For(0, (r.Data.Length > Data.Length) ? Data.Length : r.Data.Length, index => thisDataCopy[index] = Data[index]); + CommonParallel.For(0, (r.Data.Length > m.Data.Length) ? m.Data.Length : r.Data.Length, index => otherDataCopy[index] = m.Data[index]); + + 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 Complex Determinant() + { + if (RowCount != ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + return Data.Aggregate(Complex.One, (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((Complex[])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"); + } + + CommonParallel.For(0, Data.Length, index => diagonalTarget.Data[index] = Data[index]); + } + + /// + /// Returns the transpose of this matrix. + /// + /// The transpose of this matrix. + public override Matrix Transpose() + { + var ret = new DiagonalMatrix(ColumnCount, RowCount); + CommonParallel.For(0, Data.Length, index => ret.Data[index] = Data[index]); + return ret; + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public override Matrix ConjugateTranspose() + { + var ret = new DiagonalMatrix(ColumnCount, RowCount); + CommonParallel.For(0, Data.Length, index => ret.Data[index] = Data[index].Conjugate()); + 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, t.Magnitude)); + } + + /// Calculates the L2 norm. + /// The L2 norm of the matrix. + public override double L2Norm() + { + return Data.Aggregate(double.NegativeInfinity, (current, t) => Math.Max(current, t.Magnitude)); + } + + /// Calculates the Frobenius norm of this matrix. + /// The Frobenius norm of this matrix. + public override double FrobeniusNorm() + { + var norm = Data.Sum(t => t.Magnitude * t.Magnitude); + 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, Data[i].Magnitude); + minSv = Math.Min(minSv, Data[i].Magnitude); + } + + 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 Complex[,] ToArray() + { + var result = new Complex[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 + + /// + /// Negates each element of this matrix. + /// + public override void Negate() + { + Multiply(-1); + } + + /// + /// Generates matrix with random elements. + /// + /// Number of rows. + /// Number of columns. + /// Continuous Random Distribution or Source + /// + /// An numberOfRows-by-numberOfColumns matrix with elements distributed according to the provided distribution. + /// + /// If the parameter is not positive. + /// If the parameter is not positive. + public override Matrix Random(int numberOfRows, int numberOfColumns, IContinuousDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfColumns"); + } + + var matrix = CreateMatrix(numberOfRows, numberOfColumns); + CommonParallel.For( + 0, + ColumnCount, + j => + { + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, j] = new Complex(distribution.Sample(), distribution.Sample()); + } + }); + + return matrix; + } + + /// + /// Generates matrix with random elements. + /// + /// Number of rows. + /// Number of columns. + /// Continuous Random Distribution or Source + /// + /// An numberOfRows-by-numberOfColumns matrix with elements distributed according to the provided distribution. + /// + /// If the parameter is not positive. + /// If the parameter is not positive. + public override Matrix Random(int numberOfRows, int numberOfColumns, IDiscreteDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfColumns"); + } + + var matrix = CreateMatrix(numberOfRows, numberOfColumns); + CommonParallel.For( + 0, + ColumnCount, + j => + { + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, j] = new Complex(distribution.Sample(), distribution.Sample()); + } + }); + + return matrix; + } + + #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + /// + /// Subtract two values T-T + /// + /// Left operand value + /// Right operand value + /// Result of subtract + protected sealed override Complex SubtractT(Complex val1, Complex val2) + { + return val1 - val2; + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Divide two values T/T + /// + /// Left operand value + /// Right operand value + /// Result of divide + protected sealed override Complex DivideT(Complex val1, Complex val2) + { + return val1 / val2; + } + + /// + /// Is equal to one? + /// + /// Value to check + /// True if one; otherwise false + protected sealed override bool IsOneT(Complex val1) + { + return Complex.One.AlmostEqual(val1); + } + + /// + /// Take absolute value + /// + /// Source alue + /// True if one; otherwise false + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/DenseCholesky.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseCholesky.cs new file mode 100644 index 00000000..374a8a64 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseCholesky.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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Properties; + using Threading; + + /// + /// A class which encapsulates the functionality of a Cholesky factorization for dense 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 DenseCholesky : 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 DenseCholesky(DenseMatrix 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). + var factor = (DenseMatrix)matrix.Clone(); + Control.LinearAlgebraProvider.CholeskyFactor(factor.Data, factor.RowCount); + CholeskyFactor = factor; + } + + /// + /// 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) + { + // 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 != CholeskyFactor.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do Cholesky factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + CommonParallel.For(0, dinput.Data.Length, index => dresult.Data[index] = dinput.Data[index]); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)CholeskyFactor; + Control.LinearAlgebraProvider.CholeskySolveFactored(dfactor.Data, dfactor.RowCount, dresult.Data, dresult.RowCount, dresult.ColumnCount); + } + + /// + /// 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); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do Cholesky factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + CommonParallel.For(0, dinput.Data.Length, index => dresult.Data[index] = dinput.Data[index]); + + // Cholesky solve by overwriting result. + var dfactor = (DenseMatrix)CholeskyFactor; + Control.LinearAlgebraProvider.CholeskySolveFactored(dfactor.Data, dfactor.RowCount, dresult.Data, dresult.Count, 1); + } + + #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Returns the natural (base e) logarithm of a specified number. + /// + /// A number whose logarithm is to be found + /// Natural (base e) logarithm + protected sealed override Complex LogT(Complex val1) + { + return val1.NaturalLogarithm(); + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/DenseLU.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseLU.cs new file mode 100644 index 00000000..baf323fb --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseLU.cs @@ -0,0 +1,224 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Properties; + using Threading; + + /// + /// 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 DenseLU : 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 DenseLU(DenseMatrix 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. + Pivots = new int[matrix.RowCount]; + + // Create a new matrix for the LU factors, then perform factorization (while overwriting). + var factors = (DenseMatrix)matrix.Clone(); + Control.LinearAlgebraProvider.LUFactor(factors.Data, factors.RowCount, Pivots); + Factors = factors; + } + + /// + /// 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); + } + + var dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do LU factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do LU factorization for dense matrices at the moment."); + } + + // Copy the contents of input to result. + CommonParallel.For(0, dinput.Data.Length, index => dresult.Data[index] = dinput.Data[index]); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + Control.LinearAlgebraProvider.LUSolveFactored(input.ColumnCount, dfactors.Data, dfactors.RowCount, Pivots, dresult.Data); + } + + /// + /// 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); + } + + var dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do LU factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do LU factorization for dense vectors at the moment."); + } + + // Copy the contents of input to result. + CommonParallel.For(0, dinput.Data.Length, index => dresult.Data[index] = dinput.Data[index]); + + // LU solve by overwriting result. + var dfactors = (DenseMatrix)Factors; + Control.LinearAlgebraProvider.LUSolveFactored(1, dfactors.Data, dfactors.RowCount, Pivots, dresult.Data); + } + + /// + /// Returns the inverse of this matrix. The inverse is calculated using LU decomposition. + /// + /// The inverse of this matrix. + public override Matrix Inverse() + { + var result = (DenseMatrix)Factors.Clone(); + Control.LinearAlgebraProvider.LUInverseFactored(result.Data, result.RowCount, Pivots); + return result; + } + + #region Simple arithmetic of type T + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + + /// + /// Get value of type T equal to minus one + /// + /// One value + protected sealed override Complex MinusOneValueT + { + get { return -Complex.One; } + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/DenseQR.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseQR.cs new file mode 100644 index 00000000..dec02790 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseQR.cs @@ -0,0 +1,203 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + 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 DenseQR : 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. + /// If row count is less then column count + public DenseQR(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount < matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + MatrixR = matrix.Clone(); + MatrixQ = new DenseMatrix(matrix.RowCount); + Control.LinearAlgebraProvider.QRFactor(((DenseMatrix)MatrixR).Data, matrix.RowCount, matrix.ColumnCount, ((DenseMatrix)MatrixQ).Data); + } + + /// + /// 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 dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do QR factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do QR factorization for dense matrices at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixR.RowCount, MatrixR.ColumnCount, dinput.Data, input.ColumnCount, dresult.Data); + } + + /// + /// 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 dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do QR factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do QR factorization for dense vectors at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixR.RowCount, MatrixR.ColumnCount, dinput.Data, 1, dresult.Data); + } + + #region Simple arithmetic of type T + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Returns the absolute value of a specified number. + /// + /// A number whose absolute is to be found + /// Absolute value + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/DenseSvd.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseSvd.cs new file mode 100644 index 00000000..88fa07c6 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseSvd.cs @@ -0,0 +1,217 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + 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 DenseSvd : 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 DenseSvd(DenseMatrix matrix, bool computeVectors) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + ComputeVectors = computeVectors; + var nm = Math.Min(matrix.RowCount, matrix.ColumnCount); + VectorS = new DenseVector(nm); + MatrixU = new DenseMatrix(matrix.RowCount); + MatrixVT = new DenseMatrix(matrix.ColumnCount); + Control.LinearAlgebraProvider.SingularValueDecomposition(computeVectors, ((DenseMatrix)matrix.Clone()).Data, matrix.RowCount, matrix.ColumnCount, ((DenseVector)VectorS).Data, ((DenseMatrix)MatrixU).Data, ((DenseMatrix)MatrixVT).Data); + } + + /// + /// 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 dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do SVD factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do SVD factorization for dense matrices at the moment."); + } + + Control.LinearAlgebraProvider.SvdSolveFactored(MatrixU.RowCount, MatrixVT.ColumnCount, ((DenseVector)VectorS).Data, ((DenseMatrix)MatrixU).Data, ((DenseMatrix)MatrixVT).Data, dinput.Data, input.ColumnCount, dresult.Data); + } + + /// + /// 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 dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do SVD factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do SVD factorization for dense vectors at the moment."); + } + + Control.LinearAlgebraProvider.SvdSolveFactored(MatrixU.RowCount, MatrixVT.ColumnCount, ((DenseVector)VectorS).Data, ((DenseMatrix)MatrixU).Data, ((DenseMatrix)MatrixVT).Data, dinput.Data, 1, dresult.Data); + } + + #region Simple arithmetic of type T + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Returns the absolute value of a specified number. + /// + /// A number whose absolute is to be found + /// Absolute value + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs new file mode 100644 index 00000000..5e0c85ad --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Properties; + + /// + /// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. + /// Any complex square matrix A may be decomposed as A = QR where Q is an unitary 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 unitary 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 = Complex.Zero; + for (int i = 0; i < MatrixQ.RowCount; i++) + { + dot += MatrixQ.Column(k)[i].Conjugate() * MatrixQ.Column(j)[i]; + } + + 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 = Complex.One; + for (var i = 0; i < MatrixR.ColumnCount; i++) + { + det *= MatrixR.At(i, i); + if (MatrixR.At(i, i).Magnitude.AlmostEqualInDecimalPlaces(0.0, 15)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// 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 Complex[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++) + { + var s = Complex.Zero; + for (var k = 0; k < MatrixQ.RowCount; k++) + { + s += MatrixQ.At(k, i).Conjugate() * 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 Complex[MatrixQ.RowCount]; + for (var k = 0; k < MatrixQ.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < MatrixQ.ColumnCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < MatrixQ.RowCount; k++) + { + s += MatrixQ.At(k, i).Conjugate() * 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]; + } + } + + #region Simple arithmetic of type T + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Returns the absolute value of a specified number. + /// + /// A number whose absolute is to be found + /// Absolute value + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/UserCholesky.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/UserCholesky.cs new file mode 100644 index 00000000..1417c631 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/UserCholesky.cs @@ -0,0 +1,268 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Properties; + + /// + /// A class which encapsulates the functionality of a Cholesky factorization for user 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 UserCholesky : 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 UserCholesky(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 i = 0; i < CholeskyFactor.RowCount; i++) + { + var d = Complex.Zero; + for (var j = 0; j < i; j++) + { + var s = Complex.Zero; + for (var k = 0; k < j; k++) + { + s += CholeskyFactor.At(i, k) * CholeskyFactor.At(j, k).Conjugate(); + } + + s = (matrix.At(i, j) - s) / CholeskyFactor.At(j, j); + CholeskyFactor.At(i, j, s); + d += s * s.Conjugate(); + } + + d = matrix.At(i, i) - d; + if (d.Real <= 0) + { + throw new ArgumentException(Resources.ArgumentMatrixPositiveDefinite); + } + + CholeskyFactor.At(i, i, d.SquareRoot()); + for (var k = i + 1; k < CholeskyFactor.RowCount; k++) + { + CholeskyFactor.At(i, 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; + Complex 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).Conjugate() * 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; + Complex 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).Conjugate() * result[k]; + } + + result[i] = sum / CholeskyFactor.At(i, i); + } + } + + #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Returns the natural (base e) logarithm of a specified number. + /// + /// A number whose logarithm is to be found + /// Natural (base e) logarithm + protected sealed override Complex LogT(Complex val1) + { + return val1.NaturalLogarithm(); + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/UserLU.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/UserLU.cs new file mode 100644 index 00000000..a0768b70 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/UserLU.cs @@ -0,0 +1,335 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + 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 UserLU : 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 UserLU(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 Complex[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 = Complex.Zero; + 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 (vectorLUcolj[i].Magnitude > vectorLUcolj[p].Magnitude) + { + 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); + } + + #region Simple arithmetic of type T + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + + /// + /// Get value of type T equal to minus one + /// + /// One value + protected sealed override Complex MinusOneValueT + { + get { return -Complex.One; } + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/UserQR.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/UserQR.cs new file mode 100644 index 00000000..293fb4f5 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/UserQR.cs @@ -0,0 +1,366 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Linq; + using System.Numerics; + using Generic; + using Generic.Factorization; + 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 UserQR : 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 UserQR(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 Complex[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 Complex[] GenerateColumn(Matrix a, int rowStart, int rowEnd, int column) + { + var ru = rowEnd - rowStart + 1; + var u = new Complex[ru]; + + for (var i = rowStart; i <= rowEnd; i++) + { + u[i - rowStart] = a.At(i, column); + a.At(i, column, 0.0); + } + + var norm = u.Aggregate(Complex.Zero, (current, t) => current + (t.Magnitude * t.Magnitude)); + norm = norm.SquareRoot(); + + if (rowStart == rowEnd || norm.Magnitude == 0) + { + a.At(rowStart, column, -u[0]); + u[0] = Math.Sqrt(2.0); + return u; + } + + if (u[0].Magnitude != 0.0) + { + norm = norm.Magnitude * (u[0] / u[0].Magnitude); + } + + a.At(rowStart, column, -norm); + + for (var i = 0; i < ru; i++) + { + u[i] /= norm; + } + + u[0] += 1.0; + + var s = (1.0 / u[0]).SquareRoot(); + for (var i = 0; i < ru; i++) + { + u[i] = u[i].Conjugate() * s; + } + + return u; + } + + /// + /// Compute matrix Q or R + /// + /// H vectors values + /// Matrix to calculate + /// Starting row index + /// Ending row index + /// Starting column index + /// Ending column index + private static void ComputeQR(Complex[] u, Matrix a, int rowStart, int rowEnd, int columnStart, int columnEnd) + { + if (rowEnd < rowStart || columnEnd < columnStart) + { + return; + } + + var v = new Complex[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].Conjugate() * 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 column = new Complex[MatrixR.RowCount]; + for (var j = 0; j < input.ColumnCount; j++) + { + for (var k = 0; k < MatrixR.RowCount; k++) + { + column[k] = inputCopy.At(k, j); + } + + for (var i = 0; i < MatrixR.RowCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < MatrixR.RowCount; k++) + { + s += MatrixQ.At(k, i).Conjugate() * 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 < 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 < 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 Complex[MatrixR.RowCount]; + for (var k = 0; k < MatrixR.RowCount; k++) + { + column[k] = inputCopy[k]; + } + + for (var i = 0; i < MatrixR.RowCount; i++) + { + var s = Complex.Zero; + for (var k = 0; k < MatrixR.RowCount; k++) + { + s += MatrixQ.At(k, i).Conjugate() * 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]; + } + } + + #region Simple arithmetic of type T + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Returns the absolute value of a specified number. + /// + /// A number whose absolute is to be found + /// Absolute value + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/UserSvd.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/UserSvd.cs new file mode 100644 index 00000000..0b2ab5fa --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/UserSvd.cs @@ -0,0 +1,974 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + 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 UserSvd : 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 UserSvd(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 Complex[matrixCopy.ColumnCount]; + var work = new Complex[matrixCopy.RowCount]; + + int i, j; + int l, lp1; + var cs = 0.0; + var sn = 0.0; + Complex 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]. + VectorS[l] = Cnrm2Column(matrixCopy, matrixCopy.RowCount, l, l); + if (VectorS[l].Magnitude != 0.0) + { + if (matrixCopy.At(l, l).Magnitude != 0.0) + { + VectorS[l] = Csign(VectorS[l], matrixCopy.At(l, l)); + } + + CscalColumn(matrixCopy, matrixCopy.RowCount, l, l, 1.0 / VectorS[l]); + matrixCopy.At(l, l, (Complex.One + matrixCopy.At(l, l))); + } + + VectorS[l] = -VectorS[l]; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (l < nct) + { + if (VectorS[l].Magnitude != 0.0) + { + // Apply the transformation. + t = -Cdotc(matrixCopy, matrixCopy.RowCount, l, j, l) / matrixCopy.At(l, l); + if (t != Complex.Zero) + { + 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).Conjugate(); + } + + 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 = Cnrm2Vector(e, lp1); + e[l] = enorm; + if (e[l].Magnitude != 0.0) + { + if (e[lp1].Magnitude != 0.0) + { + e[l] = Csign(e[l], e[lp1]); + } + + CscalVector(e, lp1, 1.0 / e[l]); + e[lp1] = Complex.One + e[lp1]; + } + + e[l] = -e[l].Conjugate(); + if (lp1 < matrixCopy.RowCount && e[l].Magnitude != 0.0) + { + // Apply the transformation. + for (i = lp1; i < matrixCopy.RowCount; i++) + { + work[i] = Complex.Zero; + } + + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + if (e[j] != Complex.Zero) + { + 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]).Conjugate(); + if (ww != Complex.Zero) + { + 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] = Complex.Zero; + } + + if (nrtp1 < m) + { + e[nrtp1 - 1] = matrixCopy.At((nrtp1 - 1), (m - 1)); + } + + e[m - 1] = Complex.Zero; + + // If required, generate u. + if (ComputeVectors) + { + for (j = nctp1 - 1; j < ncu; j++) + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + MatrixU.At(i, j, Complex.Zero); + } + + MatrixU.At(j, j, Complex.One); + } + + for (l = nct - 1; l >= 0; l--) + { + if (VectorS[l].Magnitude != 0.0) + { + for (j = l + 1; j < ncu; j++) + { + t = -Cdotc(MatrixU, matrixCopy.RowCount, l, j, l) / MatrixU.At(l, l); + if (t != Complex.Zero) + { + for (var ii = l; ii < matrixCopy.RowCount; ii++) + { + MatrixU.At(ii, j, MatrixU.At(ii, j) + (t * MatrixU.At(ii, l))); + } + } + } + + CscalColumn(MatrixU, matrixCopy.RowCount, l, l, -1.0); + MatrixU.At(l, l, Complex.One + MatrixU.At(l, l)); + for (i = 0; i < l; i++) + { + MatrixU.At(i, l, Complex.Zero); + } + } + else + { + for (i = 0; i < matrixCopy.RowCount; i++) + { + MatrixU.At(i, l, Complex.Zero); + } + + MatrixU.At(l, l, Complex.One); + } + } + } + + // 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].Magnitude != 0.0) + { + for (j = lp1; j < matrixCopy.ColumnCount; j++) + { + t = -Cdotc(MatrixVT, matrixCopy.ColumnCount, l, j, lp1) / MatrixVT.At(lp1, l); + if (t != Complex.Zero) + { + 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, Complex.Zero); + } + + MatrixVT.At(l, l, Complex.One); + } + } + + // Transform s and e so that they are real . + for (i = 0; i < m; i++) + { + Complex r; + if (VectorS[i].Magnitude != 0.0) + { + t = VectorS[i].Magnitude; + r = VectorS[i] / t; + VectorS[i] = t; + if (i < m - 1) + { + e[i] = e[i] / r; + } + + if (ComputeVectors) + { + CscalColumn(MatrixU, matrixCopy.RowCount, i, 0, r); + } + } + + // Exit + if (i == m - 1) + { + break; + } + + if (e[i].Magnitude != 0.0) + { + t = e[i].Magnitude; + r = t / e[i]; + e[i] = t; + VectorS[i + 1] = VectorS[i + 1] * r; + if (ComputeVectors) + { + CscalColumn(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 = VectorS[l].Magnitude + VectorS[l + 1].Magnitude; + ztest = test + e[l].Magnitude; + if (ztest.AlmostEqualInDecimalPlaces(test, 15)) + { + e[l] = Complex.Zero; + 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 + e[ls].Magnitude; + } + + if (ls != l + 1) + { + test = test + e[ls - 1].Magnitude; + } + + ztest = test + VectorS[ls].Magnitude; + if (ztest.AlmostEqualInDecimalPlaces(test, 15)) + { + VectorS[ls] = Complex.Zero; + 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].Real; + e[m - 2] = Complex.Zero; + double t1; + for (var kk = l; kk < m - 1; kk++) + { + k = m - 2 - kk + l; + t1 = VectorS[k].Real; + Srotg(ref t1, ref f, ref cs, ref sn); + VectorS[k] = t1; + if (k != l) + { + f = -sn * e[k - 1].Real; + e[k - 1] = cs * e[k - 1]; + } + + if (ComputeVectors) + { + Csrot(MatrixVT, matrixCopy.ColumnCount, k, m - 1, cs, sn); + } + } + + break; + + // Split at negligible VectorS[l]. + case 2: + f = e[l - 1].Real; + e[l - 1] = Complex.Zero; + for (k = l; k < m; k++) + { + t1 = VectorS[k].Real; + Srotg(ref t1, ref f, ref cs, ref sn); + VectorS[k] = t1; + f = -sn * e[k].Real; + e[k] = cs * e[k]; + if (ComputeVectors) + { + Csrot(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, VectorS[m - 1].Magnitude); + scale = Math.Max(scale, VectorS[m - 2].Magnitude); + scale = Math.Max(scale, e[m - 2].Magnitude); + scale = Math.Max(scale, VectorS[l].Magnitude); + scale = Math.Max(scale, e[l].Magnitude); + var sm = VectorS[m - 1].Real / scale; + var smm1 = VectorS[m - 2].Real / scale; + var emm1 = e[m - 2].Real / scale; + var sl = VectorS[l].Real / scale; + var el = e[l].Real / 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++) + { + Srotg(ref f, ref g, ref cs, ref sn); + if (k != l) + { + e[k - 1] = f; + } + + f = (cs * VectorS[k].Real) + (sn * e[k].Real); + e[k] = (cs * e[k]) - (sn * VectorS[k]); + g = sn * VectorS[k + 1].Real; + VectorS[k + 1] = cs * VectorS[k + 1]; + if (ComputeVectors) + { + Csrot(MatrixVT, matrixCopy.ColumnCount, k, k + 1, cs, sn); + } + + Srotg(ref f, ref g, ref cs, ref sn); + VectorS[k] = f; + f = (cs * e[k].Real) + (sn * VectorS[k + 1].Real); + VectorS[k + 1] = (-sn * e[k]) + (cs * VectorS[k + 1]); + g = sn * e[k + 1].Real; + e[k + 1] = cs * e[k + 1]; + if (ComputeVectors && k < matrixCopy.RowCount) + { + Csrot(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].Real < 0.0) + { + VectorS[l] = -VectorS[l]; + if (ComputeVectors) + { + CscalColumn(MatrixVT, matrixCopy.ColumnCount, l, 0, -1.0); + } + } + + // Order the singular value. + while (l != mn - 1) + { + if (VectorS[l].Real >= VectorS[l + 1].Real) + { + break; + } + + t = VectorS[l]; + VectorS[l] = VectorS[l + 1]; + VectorS[l + 1] = t; + if (ComputeVectors && l < matrixCopy.ColumnCount) + { + Swap(MatrixVT, matrixCopy.ColumnCount, l, l + 1); + } + + if (ComputeVectors && l < matrixCopy.RowCount) + { + Swap(MatrixU, matrixCopy.RowCount, l, l + 1); + } + + l = l + 1; + } + + iter = 0; + m = m - 1; + break; + } + } + + if (ComputeVectors) + { + MatrixVT = MatrixVT.ConjugateTranspose(); + } + + // 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 + /// + /// Complex value z1 + /// Complex value z2 + /// Result multiplication of signum function and absolute value + private static Complex Csign(Complex z1, Complex z2) + { + return z1.Magnitude * (z2 / z2.Magnitude); + } + + /// + /// Interchanges two vectors and + /// + /// Source matrix + /// The number of rows in + /// Column A index to swap + /// Column B index to swap + private static void Swap(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 CscalColumn(Matrix a, int rowCount, int column, int rowStart, Complex 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 CscalVector(Complex[] a, int start, Complex 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 Srotg(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 Cnrm2Column(Matrix a, int rowCount, int column, int rowStart) + { + var s = 0.0; + for (var i = rowStart; i < rowCount; i++) + { + s += a.At(i, column).Magnitude * a.At(i, column).Magnitude; + } + + 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 Cnrm2Vector(Complex[] a, int rowStart) + { + var s = 0.0; + for (var i = rowStart; i < a.Length; i++) + { + s += a[i].Magnitude * a[i].Magnitude; + } + + return Math.Sqrt(s); + } + + /// + /// Calculate dot product of and conjugating the first vector. + /// + /// Source matrix + /// The number of rows in + /// Index of column A + /// Index of column B + /// Starting row index + /// Dot product value + private static Complex Cdotc(Matrix a, int rowCount, int columnA, int columnB, int rowStart) + { + var z = Complex.Zero; + for (var i = rowStart; i < rowCount; i++) + { + z += a.At(i, columnA).Conjugate() * a.At(i, columnB); + } + + 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 cos value + /// scalar sin value + private static void Csrot(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 Complex[MatrixVT.ColumnCount]; + + for (var k = 0; k < bn; k++) + { + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + var value = Complex.Zero; + if (j < mn) + { + for (var i = 0; i < MatrixU.RowCount; i++) + { + value += MatrixU.At(i, j).Conjugate() * input.At(i, k); + } + + value /= VectorS[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + var value = Complex.Zero; + for (var i = 0; i < MatrixVT.ColumnCount; i++) + { + value += MatrixVT.At(i, j).Conjugate() * 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 Complex[MatrixVT.ColumnCount]; + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + var value = Complex.Zero; + if (j < mn) + { + for (var i = 0; i < MatrixU.RowCount; i++) + { + value += MatrixU.At(i, j).Conjugate() * input[i]; + } + + value /= VectorS[j]; + } + + tmp[j] = value; + } + + for (var j = 0; j < MatrixVT.ColumnCount; j++) + { + var value = Complex.Zero; + for (var i = 0; i < MatrixVT.ColumnCount; i++) + { + value += MatrixVT.At(i, j).Conjugate() * tmp[i]; + } + + result[j] = value; + } + } + + #region Simple arithmetic of type T + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Returns the absolute value of a specified number. + /// + /// A number whose absolute is to be found + /// Absolute value + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + + /// + /// Get value of type T equal to one + /// + /// One value + protected sealed override Complex OneValueT + { + get { return Complex.One; } + } + + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/BiCgStab.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/BiCgStab.cs new file mode 100644 index 00000000..356eefca --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/BiCgStab.cs @@ -0,0 +1,521 @@ +// +// 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.Complex.Solvers.Iterative +{ + using System; + using System.Numerics; + using Generic; + using Generic.Solvers; + using Generic.Solvers.Preconditioners; + using Generic.Solvers.Status; + using Preconditioners; + using Properties; + + /// + /// 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); + + // create some temporary double variables that are needed + // to hold values in between iterations + Complex currentRho = 0; + Complex alpha = 0; + Complex 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.Real.AlmostEqual(0, 1) && currentRho.Imaginary.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)) + vecP.Add(nu.Multiply(-omega), vecP); + + vecP.Multiply(beta, vecP); + vecP.Add(residuals, vecP); + } + 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 + residuals.Add(nu.Multiply(-alpha), 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); + temp.Add(result, temp); + + // 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^ + result.Add(vecSdash.Multiply(omega), result); + result.Add(vecPdash.Multiply(alpha), result); + + t.Multiply(-omega, residuals); + residuals.Add(vecS, residuals); + + // for continuation it is necessary that omega_i != 0.0 + // If omega is only 1 ULP from zero then we fail. + if (omega.Real.AlmostEqual(0, 1) && omega.Imaginary.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); + + // residual + b + residual.Add(b, residual); + } + + /// + /// 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/Complex/Solvers/Iterative/CompositeSolver.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/CompositeSolver.cs new file mode 100644 index 00000000..0f4fca20 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/CompositeSolver.cs @@ -0,0 +1,630 @@ +// +// 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.Complex.Solvers.Iterative +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Numerics; + using System.Reflection; + using Generic; + using Generic.Solvers; + using Generic.Solvers.Status; + using Properties; + + /// + /// 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/Complex/Solvers/Iterative/GpBiCg.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/GpBiCg.cs new file mode 100644 index 00000000..2b98d81e --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/GpBiCg.cs @@ -0,0 +1,624 @@ +// +// 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.Complex.Solvers.Iterative +{ + using System; + using System.Numerics; + using Generic; + using Generic.Solvers; + using Generic.Solvers.Preconditioners; + using Generic.Solvers.Status; + using Preconditioners; + using Properties; + + /// + /// 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 + Complex beta = 0; + Complex 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); + + // 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); + + residuals.Add(temp.Multiply(beta), 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); + + y.Add(temp.Multiply(alpha), y); + + // Store the old value of t in t0 + t.CopyTo(t0); + + // t_k = r_k - alpha_k s_k + residuals.Add(s.Multiply(-alpha), 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.Real.AlmostEqual(0, 1) && cdot.Imaginary.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); + Complex 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.Real.AlmostEqual(0, 1) && ydot.Imaginary.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)) + t0.Add(u.Multiply(beta), temp); + + temp.Subtract(residuals, temp); + temp.Multiply(eta, temp); + + temp.Add(s.Multiply(sigma), u); + + // z_k = sigma_k r_k +_ eta_k z_(k-1) - alpha_k u_k + z.Multiply(eta, z); + z.Add(u.Multiply(-alpha), z); + z.Add(residuals.Multiply(sigma), z); + + // x_(k+1) = x_k + alpha_k p_k + z_k + xtemp.Add(p.Multiply(alpha), xtemp); + + xtemp.Add(z, xtemp); + + // 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); + + t.Add(y.Multiply(-eta), residuals); + + residuals.Add(c.Multiply(-sigma), residuals); + + // 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.Real.AlmostEqual(0, 1) || !sigma.Imaginary.AlmostEqual(0, 1)) ? alpha / sigma * rdash.DotProduct(residuals) / rdash.DotProduct(t0) : 0; + + // w_k = c_k + beta_k s_k + c.Add(s.Multiply(beta), 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); + + // residual + b + residual.Add(b, residual); + } + + /// + /// 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/Complex/Solvers/Iterative/MlkBiCgStab.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/MlkBiCgStab.cs new file mode 100644 index 00000000..41791696 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/MlkBiCgStab.cs @@ -0,0 +1,783 @@ +// +// 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.Complex.Solvers.Iterative +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Numerics; + using Distributions; + using Factorization; + using Generic; + using Generic.Solvers; + using Generic.Solvers.Preconditioners; + using Generic.Solvers.Status; + using Preconditioners; + using Properties; + + /// + /// 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 values + var c = new Complex[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); + + 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].Real.AlmostEqual(0, 1) && c[k - 1].Imaginary.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) + residuals.Add(w[k - 1].Multiply(-alpha), 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.Real.AlmostEqual(0, 1) && rho.Imaginary.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) + xtemp.Add(utemp.Multiply(-rho), xtemp); + + gtemp.Multiply(alpha, gtemp); + xtemp.Add(gtemp, xtemp); + + // r_(jk+1) = rho_(j+1) A u~_(jk+1) + u_(jk+1) + u.CopyTo(residuals); + + // Reuse temp + temp.Multiply(rho, temp); + residuals.Add(temp, residuals); + + // 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 + Complex 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) + zd.Add(d[s].Multiply(beta), zd); + + // z_g = z_g + beta^(jk+i)_((j-1)k+s) g_((j-1)k+s) + zg.Add(g[s].Multiply(beta), zg); + + // z_w = z_w + beta^(jk+i)_((j-1)k+s) w_((j-1)k+s) + zw.Add(w[s].Multiply(beta), zw); + } + } + + beta = rho * c[k - 1]; + if (beta.Real.AlmostEqual(0, 1) && beta.Imaginary.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)) + residuals.Add(zw.Multiply(rho), temp); + beta = -_startingVectors[0].DotProduct(temp) / beta; + + // z_g = z_g + beta^(jk+i)_((j-1)k+k) g_((j-1)k+k) + zg.Add(g[k - 1].Multiply(beta), zg); + + // z_w = rho_(j+1) (z_w + beta^(jk+i)_((j-1)k+k) w_((j-1)k+k)) + zw.Add(w[k - 1].Multiply(beta), zw); + zw.Multiply(rho, zw); + + // 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) + zd.Add(d[s].Multiply(beta), zd); + + // z_g = z_g + beta^(jk+i)_(jk+s) * g_(jk+s) + zg.Add(g[s].Multiply(beta), zg); + } + + // 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].Real.AlmostEqual(0, 1) && c[i].Imaginary.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) + u.Add(d[i].Multiply(-alpha), u); + + // 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) + xtemp.Add(gtemp.Multiply(rho * alpha), xtemp); + + // 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) + residuals.Add(w[i].Multiply(-rho * alpha), residuals); + + // 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 = new Complex[matrix.RowCount]; + var samplesRe = distribution.Samples().Take(matrix.RowCount).ToArray(); + var samplesIm = distribution.Samples().Take(matrix.RowCount).ToArray(); + for (int j = 0; j < matrix.RowCount; j++) + { + samples[j] = new Complex(samplesRe[j], samplesIm[j]); + } + + // 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), result[i]); + } + + 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); + + // residual + b + residual.Add(b, residual); + } + + /// + /// 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/Complex/Solvers/Iterative/TFQMR.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/TFQMR.cs new file mode 100644 index 00000000..7dd708db --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/TFQMR.cs @@ -0,0 +1,528 @@ +// +// 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.Complex.Solvers.Iterative +{ + using System; + using System.Numerics; + using Generic; + using Generic.Solvers; + using Generic.Solvers.Preconditioners; + using Generic.Solvers.Status; + using Preconditioners; + using Properties; + + /// + /// 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); + + // Initialize + var startNorm = input.Norm(2); + + // Define the scalars + Complex alpha = 0; + Complex eta = 0; + double theta = 0; + + var tau = startNorm; + Complex 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.Conjugate()); + if (sigma.Real.AlmostEqual(0, 1) && sigma.Imaginary.AlmostEqual(0, 1)) + { + // FAIL HERE + _iterator.IterationCancelled(); + break; + } + + // alpha = rho / sigma + alpha = rho / sigma; + + // yOdd = yEven - alpha * v + yeven.Add(v.Multiply(-alpha), 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 + pseudoResiduals.Add(uinternal.Multiply(-alpha), pseudoResiduals); + + // 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 + x.Add(d.Multiply(eta), x); + + // 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.Real.AlmostEqual(0, 1) && rho.Imaginary.AlmostEqual(0, 1)) + { + // FAIL HERE + _iterator.IterationCancelled(); + break; + } + + var rhoNew = pseudoResiduals.DotProduct(r.Conjugate()); + var beta = rhoNew / rho; + + // Update rho for the next loop + rho = rhoNew; + + // yOdd = pseudoResiduals + beta * yOdd + pseudoResiduals.Add(yodd.Multiply(beta), yeven); + + // Solve M temp = yOdd + _preconditioner.Approximate(yeven, temp); + + // uOdd = A temp + matrix.Multiply(temp, ueven); + + // v = uEven + beta * (uOdd + beta * v) + uodd.Add(v.Multiply(beta), temp); + ueven.Add(temp.Multiply(beta), 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); + + // residual + b + residual.Add(b, residual); + } + + /// + /// 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/Complex/Solvers/Iterator.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterator.cs new file mode 100644 index 00000000..81c4c76e --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterator.cs @@ -0,0 +1,329 @@ +// +// 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.Complex.Solvers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Numerics; + using Generic; + using Generic.Solvers; + using Generic.Solvers.Status; + using Generic.Solvers.StopCriterium; + using Properties; + 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/Complex/Solvers/Preconditioners/Diagonal.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/Diagonal.cs new file mode 100644 index 00000000..5f5ea777 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/Diagonal.cs @@ -0,0 +1,151 @@ +// +// 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.Complex.Solvers.Preconditioners +{ + using System; + using System.Numerics; + using Generic; + using Generic.Solvers.Preconditioners; + 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 Complex[] _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 Complex[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/Complex/Solvers/Preconditioners/Ilutp.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/Ilutp.cs new file mode 100644 index 00000000..32bdb7aa --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/Ilutp.cs @@ -0,0 +1,730 @@ +// +// 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.Complex.Solvers.Preconditioners +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using Generic; + using Generic.Solvers.Preconditioners; + 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 (workVector[j].Magnitude < _dropTolerance) + { + workVector[j] = 0.0; + } + + // Calculate the addition factor + if (workVector[j] != 0.0) + { + // vector update all in one go + _upper.Row(j, rowVector); + + // zero out columnVector[k] because we don't need that + // one anymore for k = 0 to k = j + for (var k = 0; k <= j; k++) + { + rowVector[k] = 0.0; + } + + rowVector.Multiply(workVector[j], rowVector); + workVector.Subtract(rowVector, workVector); + } + } + } + + // for j = i, .. ,n + for (var j = i; j < sparseMatrix.RowCount; j++) + { + // if w(j) <= dropTol * ||A(i,*)|| + // { + // w(j) = 0 + // } + if (workVector[j].Magnitude <= _dropTolerance * vectorNorm) + { + workVector[j] = 0.0; + } + } + + // spaceRow = spaceLeft / (n - i + 1) // Determine the space for this row + var spaceRow = spaceLeft / (sparseMatrix.RowCount - i + 1); + + // lfil = spaceRow / 2 // space for this row of L + var fillLevel = spaceRow / 2; + FindLargestItems(0, i - 1, indexSorting, workVector); + + // l(i,j) = w(j) for j = 1, .. , i -1 // only the largest lfil elements + var lowerNonZeroCount = 0; + var count = 0; + for (var j = 0; j < i; j++) + { + if ((count > fillLevel) || (indexSorting[j] == -1)) + { + break; + } + + _lower[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + lowerNonZeroCount += 1; + } + + FindLargestItems(i + 1, sparseMatrix.RowCount - 1, indexSorting, workVector); + + // lfil = spaceRow - nnz(L(i,:)) // space for this row of U + fillLevel = spaceRow - lowerNonZeroCount; + + // u(i,j) = w(j) for j = i + 1, .. , n // only the largest lfil - 1 elements + var upperNonZeroCount = 0; + count = 0; + for (var j = 0; j < sparseMatrix.RowCount - i; j++) + { + if ((count > fillLevel - 1) || (indexSorting[j] == -1)) + { + break; + } + + _upper[i, indexSorting[j]] = workVector[indexSorting[j]]; + count += 1; + upperNonZeroCount += 1; + } + + // Simply copy the diagonal element. Next step is to see if we pivot + _upper[i, i] = workVector[i]; + + // if max(U(i,i + 1: n)) > U(i,i) / pivTol then // pivot if necessary + // { + // pivot by swapping the max and the diagonal entries + // Update L, U + // Update P + // } + + // Check if we really need to pivot. If (i+1) >=(mCoefficientMatrix.Rows -1) then + // we are working on the last row. That means that there is only one number + // And pivoting is useless. Also the indexSorting array will only contain + // -1 values. + if ((i + 1) < (sparseMatrix.RowCount - 1)) + { + if (workVector[i].Magnitude < _pivotTolerance * workVector[indexSorting[0]].Magnitude) + { + // swap columns of u (which holds the values of A in the + // sections that haven't been partitioned yet. + SwapColumns(_upper, i, indexSorting[0]); + + // Update P + var temp = _pivots[i]; + _pivots[i] = _pivots[indexSorting[0]]; + _pivots[indexSorting[0]] = temp; + } + } + + // spaceLeft = spaceLeft - nnz(L(i,:)) - nnz(U(i,:)) + spaceLeft -= lowerNonZeroCount + upperNonZeroCount; + } + + for (var i = 0; i < _lower.RowCount; i++) + { + _lower[i, i] = 1.0; + } + } + + /// + /// Pivot elements in the according to internal pivot array + /// + /// Row to pivot in + 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 = Complex.Zero; + for (var j = 0; j < i; j++) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = rhs[i] - sum; + } + + // Solve U*X = Y; + for (var i = _upper.RowCount - 1; i > -1; i--) + { + _upper.Row(i, rowValues); + + var sum = Complex.Zero; + for (var j = _upper.RowCount - 1; j > i; j--) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = 1 / rowValues[i] * (lhs[i] - sum); + } + + // We have a column pivot so we only need to pivot the + // end result not the incoming right hand side vector + var temp = lhs.Clone(); + + Pivot(temp, lhs); + } + + /// + /// Pivot elements in 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/Complex/Solvers/Preconditioners/IlutpElementSorter.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/IlutpElementSorter.cs new file mode 100644 index 00000000..b073af57 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/IlutpElementSorter.cs @@ -0,0 +1,228 @@ +// +// 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.Complex.Solvers.Preconditioners +{ + using System.Numerics; + using Generic; + + /// + /// 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]].Magnitude > values[sortedIndices[child + 1]].Magnitude)) + { + child += 1; + } + + if (values[sortedIndices[root]].Magnitude <= values[sortedIndices[child]].Magnitude) + { + return; + } + + Exchange(sortedIndices, root, child); + root = child; + } + } + + /// + /// Sorts the given integers in a decreasing fashion. + /// + /// The values. + public static void SortIntegersDecreasing(int[] values) + { + HeapSortIntegers(values, values.Length); + } + + /// + /// Sort the given integers in a decreasing fashion using heapsort algorithm + /// + /// Array of values to sort + /// Length of + private static void HeapSortIntegers(int[] values, int count) + { + var start = (count / 2) - 1; + var end = count - 1; + + BuildHeap(values, start, count); + + while (end >= 0) + { + Exchange(values, end, 0); + Sift(values, 0, end); + end -= 1; + } + } + + /// + /// Build heap + /// + /// Target values array + /// Root position + /// 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/Complex/Solvers/Preconditioners/IncompleteLU.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/IncompleteLU.cs new file mode 100644 index 00000000..25d69d09 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/IncompleteLU.cs @@ -0,0 +1,261 @@ +// +// 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.Complex.Solvers.Preconditioners +{ + using System; + using System.Numerics; + using Generic; + using Generic.Solvers.Preconditioners; + 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 rowValues = new DenseVector(_decompositionLU.RowCount); + for (var i = 0; i < _decompositionLU.RowCount; i++) + { + // Clear the rowValues + rowValues.Clear(); + _decompositionLU.Row(i, rowValues); + + var sum = Complex.Zero; + for (var j = 0; j < i; j++) + { + sum += rowValues[j] * lhs[j]; + } + + lhs[i] = rhs[i] - sum; + } + + // Solve: + // Ux = z + // Which gives + // for (int i = matrix.RowLength - 1; i > -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 = Complex.Zero; + 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/Complex/Solvers/Preconditioners/UnitPreconditioner.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/UnitPreconditioner.cs new file mode 100644 index 00000000..8520a3a5 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Preconditioners/UnitPreconditioner.cs @@ -0,0 +1,140 @@ +// +// 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.Complex.Solvers.Preconditioners +{ + using System; + using System.Numerics; + using Generic; + using Generic.Solvers; + using Generic.Solvers.Preconditioners; + 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/Complex/Solvers/StopCriterium/DivergenceStopCriterium.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/DivergenceStopCriterium.cs new file mode 100644 index 00000000..ec8515d4 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/DivergenceStopCriterium.cs @@ -0,0 +1,394 @@ +// +// 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.Complex.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using System.Numerics; + using Generic; + using Generic.Solvers.Status; + using Generic.Solvers.StopCriterium; + + /// + /// 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/Complex/Solvers/StopCriterium/FailureStopCriterium.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/FailureStopCriterium.cs new file mode 100644 index 00000000..9c61ac38 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/FailureStopCriterium.cs @@ -0,0 +1,202 @@ +// +// 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.Complex.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using System.Numerics; + using Generic; + using Generic.Solvers.Status; + using Generic.Solvers.StopCriterium; + using Properties; + + /// + /// 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/Complex/Solvers/StopCriterium/IterationCountStopCriterium.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/IterationCountStopCriterium.cs new file mode 100644 index 00000000..7199b686 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/IterationCountStopCriterium.cs @@ -0,0 +1,228 @@ +// +// 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.Complex.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using System.Numerics; + using Generic; + using Generic.Solvers.Status; + using Generic.Solvers.StopCriterium; + + /// + /// 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/Complex/Solvers/StopCriterium/ResidualStopCriterium.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/ResidualStopCriterium.cs new file mode 100644 index 00000000..82e8e2e4 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/StopCriterium/ResidualStopCriterium.cs @@ -0,0 +1,410 @@ +// +// 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.Complex.Solvers.StopCriterium +{ + using System; + using System.Diagnostics; + using System.Numerics; + using Generic; + using Generic.Solvers.Status; + using Generic.Solvers.StopCriterium; + using Properties; + + /// + /// 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/Complex/SparseMatrix.cs b/src/Numerics/LinearAlgebra/Complex/SparseMatrix.cs new file mode 100644 index 00000000..1d556ad3 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/SparseMatrix.cs @@ -0,0 +1,1710 @@ +// +// 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.Complex +{ + using System; + using System.Numerics; + using Distributions; + using Generic; + using Properties; + using Threading; + + /// + /// A Matrix class with sparse storage. The underlying storage scheme is 3-array CSR Format. + /// + public class SparseMatrix : Matrix + { + /// + /// Object for use in "lock" + /// + private readonly object _lockObject = new object(); + + /// + /// The array containing the row indices of the existing rows. Element "j" of the array gives the index of the + /// element in the array that is first non-zero element in a row "j" + /// + private readonly int[] _rowIndex = new int[0]; + + /// + /// Array that contains the non-zero elements of matrix. Values of the non-zero elements of matrix are mapped into the values + /// array using the row-major storage mapping described in a compressed sparse row (CSR) format. + /// + private Complex[] _nonZeroValues = new Complex[0]; + + /// + /// Gets the number of non zero elements in the matrix. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get; + private set; + } + + /// + /// An array containing the column indices of the non-zero values. Element "I" of the array + /// is the number of the column in matrix that contains the I-th value in the array. + /// + private int[] _columnIndices = new int[0]; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + public SparseMatrix(int rows, int columns) : base(rows, columns) + { + _rowIndex = new int[rows]; + } + + /// + /// 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 SparseMatrix(int order) : this(order, order) + { + } + + /// + /// 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 SparseMatrix(int rows, int columns, Complex value) : this(rows, columns) + { + if (value == Complex.Zero) + { + return; + } + + NonZerosCount = rows * columns; + _nonZeroValues = new Complex[NonZerosCount]; + _columnIndices = new int[NonZerosCount]; + + for (int i = 0, j = 0; i < _nonZeroValues.Length; i++, j++) + { + // Reset column position to "0" + if (j == columns) + { + j = 0; + } + + _nonZeroValues[i] = value; + _columnIndices[i] = j; + } + + // Set proper row pointers + for (var i = 0; i < _rowIndex.Length; i++) + { + _rowIndex[i] = ((i + 1) * columns) - columns; + } + } + + /// + /// Initializes a new instance of the class from a one dimensional array. + /// + /// The number of rows. + /// The number of columns. + /// The one dimensional array to create this matrix from. This array should store the matrix in column-major order. + /// If length is less than * . + /// + public SparseMatrix(int rows, int columns, Complex[] array) : this(rows, columns) + { + if (rows * columns > array.Length) + { + throw new ArgumentOutOfRangeException(Resources.ArgumentMatrixDimensions); + } + + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < columns; j++) + { + SetValueAt(i, j, array[i + (j * rows)]); + } + } + } + + /// + /// Initializes a new instance of the class from a 2D array. + /// + /// The 2D array to create this matrix from. + public SparseMatrix(Complex[,] 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++) + { + SetValueAt(i, j, array[i, j]); + } + } + } + + /// + /// Creates a SparseMatrix for the given number of rows and columns. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + /// + /// A SparseMatrix with the given dimensions. + /// + public override Matrix CreateMatrix(int numberOfRows, int numberOfColumns) + { + return new SparseMatrix(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); + } + + /// + /// Returns a new matrix containing the lower triangle of this matrix. + /// + /// The lower triangle of this matrix. + public override Matrix LowerTriangle() + { + var result = CreateMatrix(RowCount, ColumnCount); + LowerTriangleImpl(result); + return result; + } + + /// + /// 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)) + { + var tmp = result.CreateMatrix(result.RowCount, result.ColumnCount); + LowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + LowerTriangleImpl(result); + } + } + + /// + /// Puts the lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void LowerTriangleImpl(Matrix result) + { + for (var row = 0; row < result.RowCount; row++) + { + var startIndex = _rowIndex[row]; + var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[row + 1] : NonZerosCount; + for (var j = startIndex; j < endIndex; j++) + { + if (row >= _columnIndices[j]) + { + result.At(row, _columnIndices[j], _nonZeroValues[j]); + } + } + } + } + + /// + /// Returns a new matrix containing the upper triangle of this matrix. + /// + /// The upper triangle of this matrix. + public override Matrix UpperTriangle() + { + var result = CreateMatrix(RowCount, ColumnCount); + UpperTriangleImpl(result); + return result; + } + + /// + /// 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"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = result.CreateMatrix(result.RowCount, result.ColumnCount); + UpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + UpperTriangleImpl(result); + } + } + + /// + /// Puts the upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void UpperTriangleImpl(Matrix result) + { + for (var row = 0; row < result.RowCount; row++) + { + var startIndex = _rowIndex[row]; + var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[row + 1] : NonZerosCount; + for (var j = startIndex; j < endIndex; j++) + { + if (row <= _columnIndices[j]) + { + result.At(row, _columnIndices[j], _nonZeroValues[j]); + } + } + } + } + + /// + /// 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 = (SparseMatrix)CreateMatrix(rowLength, columnLength); + + for (int i = rowIndex, row = 0; i < rowMax; i++, row++) + { + var startIndex = _rowIndex[i]; + var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[i + 1] : NonZerosCount; + + for (int j = startIndex; j < endIndex; j++) + { + // check if the column index is in the range + if ((_columnIndices[j] >= columnIndex) && (_columnIndices[j] < columnIndex + columnLength)) + { + var column = _columnIndices[j] - columnIndex; + result.SetValueAt(row, column, _nonZeroValues[j]); + } + } + } + + return result; + } + + /// + /// 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() + { + var result = CreateMatrix(RowCount, ColumnCount); + StrictlyLowerTriangleImpl(result); + return result; + } + + /// + /// 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"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = result.CreateMatrix(result.RowCount, result.ColumnCount); + StrictlyLowerTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyLowerTriangleImpl(result); + } + } + + /// + /// Puts the strictly lower triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyLowerTriangleImpl(Matrix result) + { + for (var row = 0; row < result.RowCount; row++) + { + var startIndex = _rowIndex[row]; + var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[row + 1] : NonZerosCount; + for (var j = startIndex; j < endIndex; j++) + { + if (row > _columnIndices[j]) + { + result.At(row, _columnIndices[j], _nonZeroValues[j]); + } + } + } + } + + /// + /// 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() + { + var result = CreateMatrix(RowCount, ColumnCount); + StrictlyUpperTriangleImpl(result); + return result; + } + + /// + /// 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"); + } + + if (ReferenceEquals(this, result)) + { + var tmp = result.CreateMatrix(result.RowCount, result.ColumnCount); + StrictlyUpperTriangle(tmp); + tmp.CopyTo(result); + } + else + { + result.Clear(); + StrictlyUpperTriangleImpl(result); + } + } + + /// + /// Puts the strictly upper triangle of this matrix into the result matrix. + /// + /// Where to store the lower triangle. + private void StrictlyUpperTriangleImpl(Matrix result) + { + for (var row = 0; row < result.RowCount; row++) + { + var startIndex = _rowIndex[row]; + var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[row + 1] : NonZerosCount; + for (var j = startIndex; j < endIndex; j++) + { + if (row < _columnIndices[j]) + { + result.At(row, _columnIndices[j], _nonZeroValues[j]); + } + } + } + } + + /// + /// Returns the matrix's elements as an array with the data laid out column-wise. + /// + ///
+        /// 1, 2, 3
+        /// 4, 5, 6  will be returned as  1, 4, 7, 2, 5, 8, 3, 6, 9
+        /// 7, 8, 9
+        /// 
+ /// An array containing the matrix's elements. + public override Complex[] ToColumnWiseArray() + { + var ret = new Complex[RowCount * ColumnCount]; + for (var j = 0; j < ColumnCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + var index = FindItem(i, j); + ret[(j * RowCount) + i] = index >= 0 ? _nonZeroValues[index] : 0.0; + } + } + + return ret; + } + + /// + /// Retrieves the requested element without range checking. + /// + /// + /// The row of the element. + /// + /// + /// The column of the element. + /// + /// + /// The requested element. + /// + public override Complex At(int row, int column) + { + lock (_lockObject) + { + var index = FindItem(row, column); + return index >= 0 ? _nonZeroValues[index] : 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. + /// + public override void At(int row, int column, Complex value) + { + lock (_lockObject) + { + SetValueAt(row, column, value); + } + } + + #region Internal methods - CRS storage implementation + /// + /// Created this method because we cannot call "virtual At" in constructor of the class, but we need to do it + /// + /// The row of the element. + /// The column of the element. + /// The value to set the element to. + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks + private void SetValueAt(int row, int column, Complex value) + { + var index = FindItem(row, column); + if (index >= 0) + { + // Non-zero item found in matrix + if (value == Complex.Zero) + { + // Delete existing item + DeleteItemByIndex(index, row); + } + else + { + // Update item + _nonZeroValues[index] = value; + } + } + else + { + // Item not found. Add new value + if (value == Complex.Zero) + { + return; + } + + index = ~index; + + // Check if the storage needs to be increased + if ((NonZerosCount == _nonZeroValues.Length) && (NonZerosCount < (RowCount * ColumnCount))) + { + // Value array is completely full so we increase the size + // Determine the increase in size. We will not grow beyond the size of the matrix + var size = Math.Min(_nonZeroValues.Length + GrowthSize(), RowCount * ColumnCount); + Array.Resize(ref _nonZeroValues, size); + Array.Resize(ref _columnIndices, size); + } + + // Move all values (with an position larger than index) in the value array to the next position + // move all values (with an position larger than index) in the columIndices array to the next position + for (var i = NonZerosCount - 1; i > index - 1; i--) + { + _nonZeroValues[i + 1] = _nonZeroValues[i]; + _columnIndices[i + 1] = _columnIndices[i]; + } + + // Add the value and the column index + _nonZeroValues[index] = value; + _columnIndices[index] = column; + + // increase the number of non-zero numbers by one + NonZerosCount += 1; + + // add 1 to all the row indices for rows bigger than rowIndex + // so that they point to the correct part of the value array again. + for (var i = row + 1; i < _rowIndex.Length; i++) + { + _rowIndex[i] += 1; + } + } + } + + /// + /// Delete value from internal storage + /// + /// Index of value in nonZeroValues array + /// Row number of matrix + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks + private void DeleteItemByIndex(int itemIndex, int row) + { + // Move all values (with an position larger than index) in the value array to the previous position + // move all values (with an position larger than index) in the columIndices array to the previous position + for (var i = itemIndex + 1; i < NonZerosCount; i++) + { + _nonZeroValues[i - 1] = _nonZeroValues[i]; + _columnIndices[i - 1] = _columnIndices[i]; + } + + // Decrease value in Row + for (var i = row + 1; i < _rowIndex.Length; i++) + { + _rowIndex[i] -= 1; + } + + NonZerosCount -= 1; + + // Check if the storage needs to be shrink. This is reasonable to do if + // there are a lot of non-zero elements and storage is two times bigger + if ((NonZerosCount > 1024) && (NonZerosCount < _nonZeroValues.Length / 2)) + { + Array.Resize(ref _nonZeroValues, NonZerosCount); + Array.Resize(ref _columnIndices, NonZerosCount); + } + } + + /// + /// Find item Index in nonZeroValues array + /// + /// Matrix row index + /// Matrix column index + /// Item index + /// WARNING: This method is not thread safe. Use "lock" with it and be sure to avoid deadlocks + private int FindItem(int row, int column) + { + // Determin bounds in columnIndices array where this item should be searched (using rowIndex) + var startIndex = _rowIndex[row]; + var endIndex = row < _rowIndex.Length - 1 ? _rowIndex[row + 1] : NonZerosCount; + return Array.BinarySearch(_columnIndices, startIndex, endIndex - startIndex, column); + } + + /// + /// Calculates the amount with which to grow the storage array's if they need to be + /// increased in size. + /// + /// The amount grown. + private int GrowthSize() + { + int delta; + if (_nonZeroValues.Length > 1024) + { + delta = _nonZeroValues.Length / 4; + } + else + { + if (_nonZeroValues.Length > 256) + { + delta = 512; + } + else + { + delta = _nonZeroValues.Length > 64 ? 128 : 32; + } + } + + return delta; + } + #endregion + + /// + /// Sets all values to zero. + /// + public override void Clear() + { + NonZerosCount = 0; + Array.Clear(_rowIndex, 0, _rowIndex.Length); + } + + /// + /// 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 sparseTarget = target as SparseMatrix; + + if (sparseTarget == null) + { + base.CopyTo(target); + } + else + { + if (ReferenceEquals(this, target)) + { + return; + } + + if (RowCount != target.RowCount || ColumnCount != target.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "target"); + } + + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + sparseTarget._nonZeroValues = new Complex[NonZerosCount]; + sparseTarget._columnIndices = new int[NonZerosCount]; + sparseTarget.NonZerosCount = NonZerosCount; + + if (NonZerosCount != 0) + { + CommonParallel.For(0, NonZerosCount, index => sparseTarget._nonZeroValues[index] = _nonZeroValues[index]); + Buffer.BlockCopy(_columnIndices, 0, sparseTarget._columnIndices, 0, NonZerosCount * Constants.SizeOfInt); + Buffer.BlockCopy(_rowIndex, 0, sparseTarget._rowIndex, 0, RowCount * Constants.SizeOfInt); + } + } + } + + /// + /// 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 sparseMatrix = obj as SparseMatrix; + + if (sparseMatrix == null) + { + return base.Equals(obj); + } + + // Accept if the argument is the same object as this + if (ReferenceEquals(this, sparseMatrix)) + { + return true; + } + + if (ColumnCount != sparseMatrix.ColumnCount || RowCount != sparseMatrix.RowCount || NonZerosCount != sparseMatrix.NonZerosCount) + { + return false; + } + + // If all else fails, perform element wise comparison. + for (var index = 0; index < NonZerosCount; index++) + { + if (!_nonZeroValues[index].AlmostEqual(sparseMatrix._nonZeroValues[index]) || _columnIndices[index] != sparseMatrix._columnIndices[index]) + { + return false; + } + } + + return true; + } + + /// + /// 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(NonZerosCount, 25); + long hash = 0; + for (var i = 0; i < hashNum; i++) + { +#if SILVERLIGHT + hash ^= Precision.DoubleToInt64Bits(_nonZeroValues[i].GetHashCode()); +#else + hash ^= BitConverter.DoubleToInt64Bits(_nonZeroValues[i].GetHashCode()); +#endif + } + + return BitConverter.ToInt32(BitConverter.GetBytes(hash), 4); + } + + /// + /// Returns the transpose of this matrix. + /// + /// The transpose of this matrix. + public override Matrix Transpose() + { + var ret = new SparseMatrix(ColumnCount, RowCount) + { + _columnIndices = new int[NonZerosCount], + _nonZeroValues = new Complex[NonZerosCount] + }; + + // Do an 'inverse' CopyTo iterate over the rows + 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; + } + + for (var j = startIndex; j < endIndex; j++) + { + ret.SetValueAt(_columnIndices[j], i, _nonZeroValues[j]); + } + } + + return ret; + } + + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public override Matrix ConjugateTranspose() + { + var ret = new SparseMatrix(ColumnCount, RowCount) + { + _columnIndices = new int[NonZerosCount], + _nonZeroValues = new Complex[NonZerosCount] + }; + + // Do an 'inverse' CopyTo iterate over the rows + 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; + } + + for (var j = startIndex; j < endIndex; j++) + { + ret.SetValueAt(_columnIndices[j], i, _nonZeroValues[j].Conjugate()); + } + } + + 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 += aat._nonZeroValues[j].Magnitude; + } + } + } + + 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 += _nonZeroValues[j].Magnitude; + } + + norm = Math.Max(norm, s); + } + + return norm; + } + + /// + /// 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"); + } + + // Determine bounds in columnIndices array where this item should be searched (using rowIndex) + var startIndex = _rowIndex[rowIndex]; + var endIndex = rowIndex < _rowIndex.Length - 1 ? _rowIndex[rowIndex + 1] : NonZerosCount; + + if (startIndex == endIndex) + { + result.Clear(); + } + else + { + // If there are non-zero elements use base class implementation + for (int i = columnIndex, j = 0; i < columnIndex + length; i++, j++) + { + // Copy code from At(row, column) to avoid unnecessary lock + var index = FindItem(rowIndex, i); + result[j] = index >= 0 ? _nonZeroValues[index] : 0.0; + } + } + } + + /// + /// Diagonally stacks this 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 (Rows + lower.rows) x (Columns + lower.Columns). + public override void DiagonalStack(Matrix lower, Matrix result) + { + var lowerSparseMatrix = lower as SparseMatrix; + var resultSparseMatrix = result as SparseMatrix; + + if ((lowerSparseMatrix == null) || (resultSparseMatrix == null)) + { + base.DiagonalStack(lower, result); + } + else + { + if (resultSparseMatrix.RowCount != RowCount + lowerSparseMatrix.RowCount || resultSparseMatrix.ColumnCount != ColumnCount + lowerSparseMatrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions, "result"); + } + + resultSparseMatrix.NonZerosCount = NonZerosCount + lowerSparseMatrix.NonZerosCount; + resultSparseMatrix._nonZeroValues = new Complex[resultSparseMatrix.NonZerosCount]; + resultSparseMatrix._columnIndices = new int[resultSparseMatrix.NonZerosCount]; + + Array.Copy(_nonZeroValues, 0, resultSparseMatrix._nonZeroValues, 0, NonZerosCount); + Array.Copy(lowerSparseMatrix._nonZeroValues, 0, resultSparseMatrix._nonZeroValues, NonZerosCount, lowerSparseMatrix.NonZerosCount); + + Array.Copy(_columnIndices, 0, resultSparseMatrix._columnIndices, 0, NonZerosCount); + Array.Copy(_rowIndex, 0, resultSparseMatrix._rowIndex, 0, RowCount); + + // Copy and adjust lower column indices and rowIndex + for (int i = NonZerosCount, j = 0; i < resultSparseMatrix.NonZerosCount; i++, j++) + { + resultSparseMatrix._columnIndices[i] = lowerSparseMatrix._columnIndices[j] + ColumnCount; + } + + for (int i = RowCount, j = 0; i < resultSparseMatrix.RowCount; i++, j++) + { + resultSparseMatrix._rowIndex[i] = lowerSparseMatrix._rowIndex[j] + NonZerosCount; + } + } + } + + #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. + public override void Add(Matrix other) + { + if (ReferenceEquals(this, other)) + { + Multiply(2); + return; + } + + var m = other as SparseMatrix; + if (m == null) + { + base.Add(other); + } + else + { + 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(SparseMatrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw new ArgumentOutOfRangeException(Resources.ArgumentMatrixDimensions); + } + + for (var i = 0; i < other.RowCount; i++) + { + // Get the begin / end index for the current row + var startIndex = other._rowIndex[i]; + var endIndex = i < other._rowIndex.Length - 1 ? other._rowIndex[i + 1] : other.NonZerosCount; + + for (var j = startIndex; j < endIndex; j++) + { + var index = FindItem(i, other._columnIndices[j]); + if (index >= 0) + { + if (_nonZeroValues[index] + other._nonZeroValues[j] == 0.0) + { + DeleteItemByIndex(index, i); + } + else + { + _nonZeroValues[index] += other._nonZeroValues[j]; + } + } + else + { + SetValueAt(i, other._columnIndices[j], other._nonZeroValues[j]); + } + } + } + } + + /// + /// 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. + public override void Subtract(Matrix other) + { + // We are substracting Matrix form itself + if (ReferenceEquals(this, other)) + { + Clear(); + return; + } + + var m = other as SparseMatrix; + if (m == null) + { + base.Subtract(other); + } + else + { + 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(SparseMatrix other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (other.RowCount != RowCount || other.ColumnCount != ColumnCount) + { + throw new ArgumentOutOfRangeException(Resources.ArgumentMatrixDimensions); + } + + for (var i = 0; i < other.RowCount; i++) + { + // Get the begin / end index for the current row + var startIndex = other._rowIndex[i]; + var endIndex = i < other._rowIndex.Length - 1 ? other._rowIndex[i + 1] : other.NonZerosCount; + + for (var j = startIndex; j < endIndex; j++) + { + var index = FindItem(i, other._columnIndices[j]); + if (index >= 0) + { + if (_nonZeroValues[index] - other._nonZeroValues[j] == 0.0) + { + DeleteItemByIndex(index, i); + } + else + { + _nonZeroValues[index] -= other._nonZeroValues[j]; + } + } + else + { + SetValueAt(i, other._columnIndices[j], -other._nonZeroValues[j]); + } + } + } + } + + /// + /// Multiplies each element of this matrix with a complex. + /// + /// The complex to multiply with. + public override void Multiply(Complex complex) + { + if (Complex.One.AlmostEqual(complex)) + { + return; + } + + if (Complex.Zero.AlmostEqual(complex)) + { + Clear(); + return; + } + + Control.LinearAlgebraProvider.ScaleArray(complex, _nonZeroValues); + } + + /// + /// Multiplies this sparse matrix with another sparse matrix and places the results into the result sparse matrix. + /// + /// The matrix to multiply with. + /// The result of the multiplication. + /// If the other matrix is . + /// If the result matrix is . + /// If Columns != other.Rows. + /// If the result matrix's dimensions are not the Rows x other.Columns. + public override void Multiply(Matrix other, Matrix result) + { + var otherSparseMatrix = other as SparseMatrix; + var resultSparseMatrix = result as SparseMatrix; + if (otherSparseMatrix == null || resultSparseMatrix == null) + { + base.Multiply(other, result); + return; + } + + if (ColumnCount != otherSparseMatrix.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + 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) + { + 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); + } + } + } + + /// + /// Multiplies this matrix with another matrix and returns the result. + /// + /// The matrix to multiply with. + /// If Columns != other.Rows. + /// If the other matrix is . + /// The result of multiplication. + public override Matrix Multiply(Matrix other) + { + var matrix = other as SparseMatrix; + if (matrix == null) + { + return base.Multiply(other); + } + + if (ColumnCount != matrix.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + var result = (SparseMatrix)CreateMatrix(RowCount, matrix.ColumnCount); + Multiply(matrix, result); + 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. + /// + /// 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 SparseMatrix operator *(SparseMatrix leftSide, SparseMatrix 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 (SparseMatrix)leftSide.Multiply(rightSide); + } + + /// + /// 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"); + } + + result.Clear(); + for (var i = 0; i < other.RowCount; i++) + { + // Get the begin / end index for the current row + var startIndex = _rowIndex[i]; + var endIndex = i < _rowIndex.Length - 1 ? _rowIndex[i + 1] : NonZerosCount; + + for (var j = startIndex; j < endIndex; j++) + { + var resVal = _nonZeroValues[j] * other[i, _columnIndices[j]]; + if (resVal != 0.0) + { + result[i, _columnIndices[j]] = resVal; + } + } + } + } + + /// + /// Generates matrix with random elements. + /// + /// Number of rows. + /// Number of columns. + /// Continuous Random Distribution or Source + /// + /// An numberOfRows-by-numberOfColumns matrix with elements distributed according to the provided distribution. + /// + /// If the parameter is not positive. + /// If the parameter is not positive. + public override Matrix Random(int numberOfRows, int numberOfColumns, IContinuousDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfColumns"); + } + + var matrix = (SparseMatrix)CreateMatrix(numberOfRows, numberOfColumns); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + var value = new Complex(distribution.Sample(), distribution.Sample()); + if (value != 0.0) + { + matrix.SetValueAt(i, j, value); + } + } + } + + return matrix; + } + + /// + /// Generates matrix with random elements. + /// + /// Number of rows. + /// Number of columns. + /// Continuous Random Distribution or Source + /// + /// An numberOfRows-by-numberOfColumns matrix with elements distributed according to the provided distribution. + /// + /// If the parameter is not positive. + /// If the parameter is not positive. + public override Matrix Random(int numberOfRows, int numberOfColumns, IDiscreteDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "numberOfColumns"); + } + + var matrix = (SparseMatrix)CreateMatrix(numberOfRows, numberOfColumns); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + var value = new Complex(distribution.Sample(), distribution.Sample()); + if (value != 0.0) + { + matrix.SetValueAt(i, j, value); + } + } + } + + return matrix; + } + + #endregion + + #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. + /// Identity SparseMatrix + /// + /// If is less than one. + /// + public static SparseMatrix Identity(int order) + { + var m = new SparseMatrix(order) + { + NonZerosCount = order, + _nonZeroValues = new Complex[order], + _columnIndices = new int[order] + }; + + for (var i = 0; i < order; i++) + { + m._nonZeroValues[i] = Complex.One; + m._columnIndices[i] = i; + m._rowIndex[i] = i; + } + + return m; + } + #endregion + + /// + /// Negates each element of this matrix. + /// + public override void Negate() + { + Multiply(-1); + } + + #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + /// + /// Subtract two values T-T + /// + /// Left operand value + /// Right operand value + /// Result of subtract + protected sealed override Complex SubtractT(Complex val1, Complex val2) + { + return val1 - val2; + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Divide two values T/T + /// + /// Left operand value + /// Right operand value + /// Result of divide + protected sealed override Complex DivideT(Complex val1, Complex val2) + { + return val1 / val2; + } + + /// + /// Is equal to one? + /// + /// Value to check + /// True if one; otherwise false + protected sealed override bool IsOneT(Complex val1) + { + return Complex.One.AlmostEqual(val1); + } + + /// + /// Take absolute value + /// + /// Source alue + /// True if one; otherwise false + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/SparseVector.cs b/src/Numerics/LinearAlgebra/Complex/SparseVector.cs new file mode 100644 index 00000000..ad0e11cd --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/SparseVector.cs @@ -0,0 +1,1695 @@ +// +// 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.Complex +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using Distributions; + using Generic; + using NumberTheory; + using Properties; + using Threading; + + /// + /// A vector with sparse storage. + /// + public class SparseVector : Vector + { + /// + /// Lock object for the indexer. + /// + private readonly object _lockObject = new object(); + + /// + /// Gets the vector's internal data. The array containing the actual values; only the non-zero values are stored. + /// + private Complex[] _nonZeroValues = new Complex[0]; + + /// + /// The indices of the non-zero entries. + /// + private int[] _nonZeroIndices = new int[0]; + + /// + /// Gets the number of non zero elements in the vector. + /// + /// The number of non zero elements. + public int NonZerosCount + { + get; + private set; + } + + #region Constructors + + /// + /// Initializes a new instance of the class with a given size. + /// + /// + /// the size of the vector. + /// + /// + /// If is less than one. + /// + public SparseVector(int size) : base(size) + { + } + + /// + /// Initializes a new instance of the class with a given size + /// and each element set to the given value; + /// + /// + /// the size of the vector. + /// + /// + /// the value to set each element to. + /// + /// + /// If is less than one. + /// + public SparseVector(int size, Complex value) : this(size) + { + if (value == Complex.Zero) + { + // Skip adding values + return; + } + + // We already know that this vector is "full", let's allocate all needed memory + _nonZeroValues = new Complex[size]; + _nonZeroIndices = new int[size]; + NonZerosCount = size; + + CommonParallel.For( + 0, + Count, + index => + { + _nonZeroValues[index] = value; + _nonZeroIndices[index] = index; + }); + } + + /// + /// Initializes a new instance of the class by + /// copying the values from another. + /// + /// + /// The vector to create the new vector from. + /// + public SparseVector(Vector other) : this(other.Count) + { + var vector = other as SparseVector; + if (vector == null) + { + for (var i = 0; i < other.Count; i++) + { + this[i] = other[i]; + } + } + else + { + _nonZeroValues = new Complex[vector.NonZerosCount]; + _nonZeroIndices = new int[vector.NonZerosCount]; + NonZerosCount = vector.NonZerosCount; + + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + if (vector.NonZerosCount != 0) + { + CommonParallel.For(0, vector.NonZerosCount, index => _nonZeroValues[index] = vector._nonZeroValues[index]); + Buffer.BlockCopy(vector._nonZeroIndices, 0, _nonZeroIndices, 0, vector.NonZerosCount * Constants.SizeOfInt); + } + } + } + + /// + /// Initializes a new instance of the class by + /// copying the values from another. + /// + /// + /// The vector to create the new vector from. + /// + public SparseVector(SparseVector other) : this(other.Count) + { + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + _nonZeroValues = new Complex[other.NonZerosCount]; + _nonZeroIndices = new int[other.NonZerosCount]; + NonZerosCount = other.NonZerosCount; + + if (other.NonZerosCount != 0) + { + CommonParallel.For(0, other.NonZerosCount, index => _nonZeroValues[index] = other._nonZeroValues[index]); + Buffer.BlockCopy(other._nonZeroIndices, 0, _nonZeroIndices, 0, other.NonZerosCount * Constants.SizeOfInt); + } + } + + /// + /// Initializes a new instance of the class for an array. + /// + /// The array to create this vector from. + /// The vector copy the array. Any changes to the vector will NOT change the array. + public SparseVector(Complex[] array) : this(array.Length) + { + for (var i = 0; i < array.Length; i++) + { + this[i] = array[i]; + } + } + + #endregion + + /// + /// Create a matrix based on this vector in column form (one single column). + /// + /// This vector as a column matrix. + public override Matrix ToColumnMatrix() + { + var matrix = new SparseMatrix(Count, 1); + for (var i = 0; i < NonZerosCount; i++) + { + matrix[_nonZeroIndices[i], 0] = _nonZeroValues[i]; + } + + return matrix; + } + + /// + /// Create a matrix based on this vector in row form (one single row). + /// + /// This vector as a row matrix. + public override Matrix ToRowMatrix() + { + var matrix = new SparseMatrix(1, Count); + for (var i = 0; i < NonZerosCount; i++) + { + matrix[0, _nonZeroIndices[i]] = _nonZeroValues[i]; + } + + return matrix; + } + + /// Gets or sets the value at the given . + /// The index of the value to get or set. + /// The value of the vector at the given . + /// If is negative or + /// greater than the size of the vector. + public override Complex this[int index] + { + get + { + // If index is out of bounds + if ((index < 0) || (index >= Count)) + { + throw new IndexOutOfRangeException(); + } + + lock (_lockObject) + { + // Search if item idex exists in NonZeroIndices array in range "0 - complex nonzero values count" + var itemIndex = Array.BinarySearch(_nonZeroIndices, 0, NonZerosCount, index); + if (itemIndex >= 0) + { + return _nonZeroValues[itemIndex]; + } + } + + return Complex.Zero; + } + + set + { + // If index is out of bounds + if ((index < 0) || (index >= Count)) + { + throw new IndexOutOfRangeException(); + } + + lock (_lockObject) + { + SetValue(index, value); + } + } + } + + /// + /// Creates a matrix with the given dimensions using the same storage type + /// as this vector. + /// + /// + /// The number of rows. + /// + /// + /// The number of columns. + /// + /// + /// A matrix with the given dimensions. + /// + public override Matrix CreateMatrix(int rows, int columns) + { + return new SparseMatrix(rows, columns); + } + + /// + /// Creates a Vector of the given size using the same storage type + /// as this vector. + /// + /// + /// The size of the Vector to create. + /// + /// + /// The new Vector. + /// + public override Vector CreateVector(int size) + { + return new SparseVector(size); + } + + /// + /// Resets all values to zero. + /// + public override void Clear() + { + NonZerosCount = 0; + } + + /// + /// Copies the values of this vector into the target vector. + /// + /// + /// The vector to copy elements into. + /// + /// + /// If is . + /// + /// + /// If is not the same size as this vector. + /// + public override void CopyTo(Vector target) + { + if (target == null) + { + throw new ArgumentNullException("target"); + } + + if (Count != target.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "target"); + } + + if (ReferenceEquals(this, target)) + { + return; + } + + var otherVector = target as SparseVector; + if (otherVector == null) + { + CommonParallel.For( + 0, + Count, + index => target[index] = this[index]); + } + else + { + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + otherVector._nonZeroValues = new Complex[NonZerosCount]; + otherVector._nonZeroIndices = new int[NonZerosCount]; + otherVector.NonZerosCount = NonZerosCount; + + if (NonZerosCount != 0) + { + CommonParallel.For(0, NonZerosCount, index => otherVector._nonZeroValues[index] = _nonZeroValues[index]); + Buffer.BlockCopy(_nonZeroIndices, 0, otherVector._nonZeroIndices, 0, NonZerosCount * Constants.SizeOfInt); + } + } + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + public override void Conjugate(Vector target) + { + if (target == null) + { + throw new ArgumentNullException("target"); + } + + if (Count != target.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "target"); + } + + if (ReferenceEquals(this, target)) + { + var tmp = CreateVector(Count); + Conjugate(tmp); + tmp.CopyTo(target); + } + + var otherVector = target as SparseVector; + if (otherVector == null) + { + CommonParallel.For( + 0, + Count, + index => target[index] = this[index].Conjugate()); + } + else + { + // Lets copy only needed data. Portion of needed data is determined by NonZerosCount value + otherVector._nonZeroValues = new Complex[NonZerosCount]; + otherVector._nonZeroIndices = new int[NonZerosCount]; + otherVector.NonZerosCount = NonZerosCount; + + if (NonZerosCount != 0) + { + CommonParallel.For(0, NonZerosCount, index => otherVector._nonZeroValues[index] = _nonZeroValues[index].Conjugate()); + Buffer.BlockCopy(_nonZeroIndices, 0, otherVector._nonZeroIndices, 0, NonZerosCount * Constants.SizeOfInt); + } + } + } + #region Operators and supplementary functions + + /// + /// Adds a complex to each element of the vector. + /// + /// The complex to add. + /// A copy of the vector with the complex added. + public override Vector Add(Complex complex) + { + if (complex == Complex.Zero) + { + return Clone(); + } + + var copy = (SparseVector)Clone(); + for (var i = 0; i < Count; i++) + { + copy[i] += complex; + } + + return copy; + } + + /// + /// Adds a complex to each element of the vector and stores the result in the result vector. + /// + /// The complex to add. + /// The vector to store the result of the addition. + /// If the result vector is . + /// If this vector and are not the same size. + public override void Add(Complex complex, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + base.Add(complex, result); + } + + /// + /// Adds another vector to this vector. + /// + /// The vector to add to this one. + /// A new vector containing the sum of both vectors. + /// If the other vector is . + /// If this vector and are not the same size. + public override Vector Add(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var sparseVector = other as SparseVector; + + if (sparseVector == null) + { + return base.Add(other); + } + + var copy = (SparseVector)Clone(); + copy.AddScaledSparseVector(Complex.One, sparseVector); + return copy; + } + + /// + /// Adds the scaled sparse vector. + /// + /// The alpha. + /// The other. + private void AddScaledSparseVector(Complex alpha, SparseVector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (alpha == Complex.Zero) + { + return; + } + + // I don't use ILinearAlgebraProvider because we will get no benefit due to "lock" in this[index] + // Possible fucniton in ILinearAlgebraProvider may be AddSparseVectorToScaledSparseVector(T[] y, int[] yIndices, T alpha, T[] x, int[] xIndices); + // But it require to develop value setting algorithm and due to "lock" it will be even more greedy then implemented below + if (ReferenceEquals(this, other)) + { + // Adding the same instance of sparse vector. That means if we modify "this" then "other" will be modified too. + // To avoid such problem lets change values in internal storage of "this" + if (alpha == Complex.One) + { + for (var i = 0; i < NonZerosCount; i++) + { + _nonZeroValues[i] += _nonZeroValues[i]; + } + } + else if (alpha == -Complex.One) + { + Clear(); // Vector is subtracted from itself + return; + } + else + { + for (var i = 0; i < NonZerosCount; i++) + { + _nonZeroValues[i] += alpha * _nonZeroValues[i]; + } + } + } + else + { + // "this" and "other" are different objects, so by modifying "this" the "other" object will not be changed + if (alpha == Complex.One) + { + for (var i = 0; i < other.NonZerosCount; i++) + { + this[other._nonZeroIndices[i]] += other._nonZeroValues[i]; + } + } + else + { + for (var i = 0; i < other.NonZerosCount; i++) + { + this[other._nonZeroIndices[i]] += alpha * other._nonZeroValues[i]; + } + } + } + } + + /// + /// Adds another vector to this vector and stores the result into the result vector. + /// + /// The vector to add to this one. + /// The vector to store the result of the addition. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void Add(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Add(other); + tmp.CopyTo(result); + } + else + { + var sparse = result as SparseVector; + if (sparse == null) + { + base.Add(other, result); + } + else + { + var sparseother = other as SparseVector; + if (sparseother == null) + { + sparse.AddScaledSparseVector(Complex.One, sparseother); + } + else + { + base.Add(other, result); + } + } + } + } + + /// + /// Returns a Vector containing the same values of . + /// + /// This method is included for completeness. + /// The vector to get the values from. + /// A vector containing a the same values as . + /// If is . + public static Vector operator +(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return rightSide.Plus(); + } + + /// + /// Adds two Vectors together and returns the results. + /// + /// One of the vectors to add. + /// The other vector to add. + /// The result of the addition. + /// If and are not the same size. + /// If or is . + public static Vector operator +(SparseVector leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + return leftSide.Add(rightSide); + } + + /// + /// Subtracts a complex from each element of the vector. + /// + /// The complex to subtract. + /// A new vector containing the subtraction of this vector and the complex. + public override Vector Subtract(Complex complex) + { + if (complex == Complex.Zero) + { + return Clone(); + } + + var copy = (SparseVector)Clone(); + for (var i = 0; i < Count; i++) + { + copy[i] -= complex; + } + + return copy; + } + + /// + /// Subtracts a complex from each element of the vector and stores the result in the result vector. + /// + /// The complex to subtract. + /// The vector to store the result of the subtraction. + /// If the result vector is . + /// If this vector and are not the same size. + public override void Subtract(Complex complex, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + base.Subtract(complex, result); + } + + /// + /// Subtracts another vector from this vector. + /// + /// The vector to subtract from this one. + /// A new vector containing the subtraction of the the two vectors. + /// If the other vector is . + /// If this vector and are not the same size. + public override Vector Subtract(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var sparseVector = other as SparseVector; + + if (sparseVector == null) + { + return base.Subtract(other); + } + + var copy = (SparseVector)Clone(); + copy.AddScaledSparseVector(-Complex.One, sparseVector); + return copy; + } + + /// + /// Subtracts another vector to this vector and stores the result into the result vector. + /// + /// The vector to subtract from this one. + /// The vector to store the result of the subtraction. + /// If the other vector is . + /// If the result vector is . + /// If this vector and are not the same size. + /// If this vector and are not the same size. + public override void Subtract(Vector other, Vector result) + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + if (Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "result"); + } + + if (ReferenceEquals(this, result) || ReferenceEquals(other, result)) + { + var tmp = Subtract(other); + tmp.CopyTo(result); + } + else + { + var sparse = result as SparseVector; + if (sparse == null) + { + base.Subtract(other, result); + } + else + { + var sparseother = other as SparseVector; + if (sparseother == null) + { + sparse.AddScaledSparseVector(-Complex.One, sparseother); + } + else + { + base.Subtract(other, result); + } + } + } + } + + /// + /// Returns a Vector containing the negated values of . + /// + /// The vector to get the values from. + /// A vector containing the negated values as . + /// If is . + public static Vector operator -(SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return rightSide.Negate(); + } + + /// + /// Subtracts two Vectors and returns the results. + /// + /// The vector to subtract from. + /// The vector to subtract. + /// The result of the subtraction. + /// If and are not the same size. + /// If or is . + public static Vector operator -(SparseVector leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + return leftSide.Subtract(rightSide); + } + + /// + /// Returns a negated vector. + /// + /// The negated vector. + /// Added as an alternative to the unary negation operator. + public override Vector Negate() + { + var result = new SparseVector(Count) + { + _nonZeroValues = new Complex[NonZerosCount], + _nonZeroIndices = new int[NonZerosCount], + NonZerosCount = NonZerosCount + }; + + if (NonZerosCount != 0) + { + CommonParallel.For( + 0, + NonZerosCount, + index => result._nonZeroValues[index] = -_nonZeroValues[index]); + Buffer.BlockCopy(_nonZeroIndices, 0, result._nonZeroIndices, 0, NonZerosCount * Constants.SizeOfInt); + } + + return result; + } + + /// + /// Multiplies a complex to each element of the vector. + /// + /// The complex to multiply. + /// A new vector that is the multiplication of the vector and the complex. + public override Vector Multiply(Complex complex) + { + if (complex == Complex.One) + { + return Clone(); + } + + if (complex == Complex.Zero) + { + var copy = Clone(); + copy.Clear(); // Set array empty + return copy; + } + else + { + var copy = (SparseVector)Clone(); + Control.LinearAlgebraProvider.ScaleArray(complex, copy._nonZeroValues); + return copy; + } + } + + /// + /// Computes the dot product between this vector and another vector. + /// + /// The other vector to add. + /// The result of the addition. + /// If is not of the same size. + /// If is . + public override Complex DotProduct(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var result = Complex.Zero; + + // base implementation iterates though all elements, but we need only take non-zeros + for (var i = 0; i < NonZerosCount; i++) + { + result += _nonZeroValues[i] * other[_nonZeroIndices[i]]; + } + + return result; + } + + /// + /// Multiplies a vector with a complex. + /// + /// The vector to scale. + /// The complex value. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(SparseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + return (SparseVector)leftSide.Multiply(rightSide); + } + + /// + /// Multiplies a vector with a complex. + /// + /// The complex value. + /// The vector to scale. + /// The result of the multiplication. + /// If is . + public static SparseVector operator *(Complex leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + return (SparseVector)rightSide.Multiply(leftSide); + } + + /// + /// Computes the dot product between two Vectors. + /// + /// The left row vector. + /// The right column vector. + /// The dot product between the two vectors. + /// If and are not the same size. + /// If or is . + public static Complex operator *(SparseVector leftSide, SparseVector rightSide) + { + if (rightSide == null) + { + throw new ArgumentNullException("rightSide"); + } + + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + if (leftSide.Count != rightSide.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "rightSide"); + } + + return leftSide.DotProduct(rightSide); + } + + /// + /// Divides a vector with a complex. + /// + /// The vector to divide. + /// The complex value. + /// The result of the division. + /// If is . + public static SparseVector operator /(SparseVector leftSide, Complex rightSide) + { + if (leftSide == null) + { + throw new ArgumentNullException("leftSide"); + } + + return (SparseVector)leftSide.Multiply(Complex.One / rightSide); + } + + /// + /// Returns the index of the absolute minimum element. + /// + /// The index of absolute minimum element. + public override int AbsoluteMinimumIndex() + { + if (NonZerosCount == 0) + { + // No non-zero elements. Return 0 + return 0; + } + + var index = 0; + var min = _nonZeroValues[index].Magnitude; + for (var i = 1; i < NonZerosCount; i++) + { + var test = _nonZeroValues[i].Magnitude; + if (test < min) + { + index = i; + min = test; + } + } + + return _nonZeroIndices[index]; + } + + /// + /// Creates a vector containing specified elements. + /// + /// The first element to begin copying from. + /// The number of elements to copy. + /// A vector containing a copy of the specified elements. + /// If is not positive or + /// greater than or equal to the size of the vector. + /// If + is greater than or equal to the size of the vector. + /// + /// If is not positive. + public override Vector SubVector(int index, int length) + { + if (index < 0 || index >= Count) + { + throw new ArgumentOutOfRangeException("index"); + } + + if (length <= 0) + { + throw new ArgumentOutOfRangeException("length"); + } + + if (index + length > Count) + { + throw new ArgumentOutOfRangeException("length"); + } + + var result = new SparseVector(length); + for (var i = index; i < index + length; i++) + { + result[i - index] = this[i]; + } + + return result; + } + + /// + /// Set the values of this vector to the given values. + /// + /// The array containing the values to use. + /// If is . + /// If is not the same size as this vector. + public override void SetValues(Complex[] values) + { + if (values == null) + { + throw new ArgumentNullException("values"); + } + + if (values.Length != Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "values"); + } + + for (var i = 0; i < values.Length; i++) + { + this[i] = values[i]; + } + } + + /// + /// Computes the sum of the vector's elements. + /// + /// The sum of the vector's elements. + public override Complex Sum() + { + var result = Complex.Zero; + for (var i = 0; i < NonZerosCount; i++) + { + result += _nonZeroValues[i]; + } + + return result; + } + + /// + /// Computes the sum of the absolute value of the vector's elements. + /// + /// The sum of the absolute value of the vector's elements. + public override double SumMagnitudes() + { + double result = 0; + for (var i = 0; i < NonZerosCount; i++) + { + result += _nonZeroValues[i].Magnitude; + } + + return result; + } + + /// + /// Pointwise multiplies this vector with another vector. + /// + /// The vector to pointwise multiply with this one. + /// A new vector which is the pointwise multiplication of the two vectors. + /// If the other vector is . + /// If this vector and are not the same size. + public override Vector PointwiseMultiply(Vector other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count != other.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength, "other"); + } + + var copy = new SparseVector(Count); + for (var i = 0; i < _nonZeroIndices.Length; i++) + { + var d = _nonZeroValues[i] * other[_nonZeroIndices[i]]; + if (d != Complex.Zero) + { + copy[_nonZeroIndices[i]] = d; + } + } + + return copy; + } + + /// + /// Outer product of two vectors + /// + /// First vector + /// Second vector + /// Matrix M[i,j] = u[i]*v[j] + /// If the u vector is . + /// If the v vector is . + public static Matrix /*SparseMatrix*/ OuterProduct(SparseVector u, SparseVector v) + { + if (u == null) + { + throw new ArgumentNullException("u"); + } + + if (v == null) + { + throw new ArgumentNullException("v"); + } + + var matrix = new SparseMatrix(u.Count, v.Count); + for (var i = 0; i < u.NonZerosCount; i++) + { + for (var j = 0; j < v.NonZerosCount; j++) + { + if (u._nonZeroIndices[i] == v._nonZeroIndices[j]) + { + matrix.At(i, j, u._nonZeroValues[i] * v._nonZeroValues[j]); + } + } + } + + return matrix; + } + + /// + /// Generates a vector with random elements + /// + /// Number of elements in the vector. + /// Continuous Random Distribution or Source + /// + /// A vector with n-random elements distributed according + /// to the specified random distribution. + /// + /// If the length vector is non positive. + public override Vector Random(int length, IContinuousDistribution randomDistribution) + { + if (length < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "length"); + } + + var v = (SparseVector)CreateVector(length); + for (var index = 0; index < v.Count; index++) + { + v[index] = randomDistribution.Sample(); + } + + return v; + } + + /// + /// Generates a vector with random elements + /// + /// Number of elements in the vector. + /// Continuous Random Distribution or Source + /// + /// A vector with n-random elements distributed according + /// to the specified random distribution. + /// + /// If the n vector is non positive. + public override Vector Random(int length, IDiscreteDistribution randomDistribution) + { + if (length < 1) + { + throw new ArgumentException(Resources.ArgumentMustBePositive, "length"); + } + + var v = (SparseVector)CreateVector(length); + for (var index = 0; index < v.Count; index++) + { + v[index] = new Complex(randomDistribution.Sample(), randomDistribution.Sample()); + } + + return v; + } + + /// + /// Tensor Product (Outer) of this and another vector. + /// + /// The vector to operate on. + /// + /// Matrix M[i,j] = this[i] * v[j]. + /// + /// + public Matrix TensorMultiply(SparseVector v) + { + return OuterProduct(this, v); + } + + #endregion + + #region Vector Norms + + /// + /// Computes the p-Norm. + /// + /// The p value. + /// Scalar ret = (sum(abs(this[i])^p))^(1/p) + public override double Norm(double p) + { + if (1 > p) + { + throw new ArgumentOutOfRangeException("p"); + } + + if (NonZerosCount == 0) + { + return 0.0; + } + + if (Double.IsPositiveInfinity(p)) + { + return CommonParallel.Select(0, NonZerosCount, (index, localData) => Math.Max(localData, _nonZeroValues[index].Magnitude), Math.Max); + } + + var sum = CommonParallel.Aggregate( + 0, + NonZerosCount, + index => Math.Pow(_nonZeroValues[index].Magnitude, p)); + + return Math.Pow(sum, 1.0 / p); + } + + /// + /// Normalizes this vector to a unit vector with respect to the p-norm. + /// + /// + /// The p value. + /// + /// + /// This vector normalized to a unit vector with respect to the p-norm. + /// + public override Vector Normalize(double p) + { + if (p < 0.0) + { + throw new ArgumentOutOfRangeException("p"); + } + + var norm = Norm(p); + var clone = Clone(); + if (norm == 0.0) + { + return clone; + } + + clone.Multiply(1.0 / norm, clone); + + return clone; + } + #endregion + + #region Parse Functions + + /// + /// Creates a double sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n,n,..', '(n,n,..)', '[n,n,...]', where n is a Complex. + /// + /// + /// A double sparse vector containing the values specified by the given string. + /// + /// + /// The string to parse. + /// + public static SparseVector Parse(string value) + { + return Parse(value, null); + } + + /// + /// Creates a double sparse vector based on a string. The string can be in the following formats (without the + /// quotes): 'n', 'n;n;..', '(n;n;..)', '[n;n;...]', where n is a Complex. + /// + /// + /// A double sparse vector containing the values specified by the given string. + /// + /// + /// the string to parse. + /// + /// + /// An that supplies culture-specific formatting information. + /// + public static SparseVector Parse(string value, IFormatProvider formatProvider) + { + if (value == null) + { + throw new ArgumentNullException(value); + } + + value = value.Trim(); + if (value.Length == 0) + { + throw new FormatException(); + } + + // strip out parens + if (value.StartsWith("(", StringComparison.Ordinal)) + { + if (!value.EndsWith(")", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + if (value.StartsWith("[", StringComparison.Ordinal)) + { + if (!value.EndsWith("]", StringComparison.Ordinal)) + { + throw new FormatException(); + } + + value = value.Substring(1, value.Length - 2).Trim(); + } + + // keywords + var textInfo = formatProvider.GetTextInfo(); + var keywords = new[] { textInfo.ListSeparator }; + + // lexing + var tokens = new LinkedList(); + GlobalizationHelper.Tokenize(tokens.AddFirst(value), keywords, 0); + var token = tokens.First; + + if (token == null || tokens.Count.IsEven()) + { + throw new FormatException(); + } + + // parsing + var data = new Complex[(tokens.Count + 1) >> 1]; + for (var i = 0; i < data.Length; i++) + { + if (token == null || token.Value == textInfo.ListSeparator) + { + throw new FormatException(); + } + + data[i] = token.Value.ToComplex(formatProvider); + + token = token.Next; + if (token != null) + { + token = token.Next; + } + } + + return new SparseVector(data); + } + + /// + /// Converts the string representation of a complex sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, out SparseVector result) + { + return TryParse(value, null, out result); + } + + /// + /// Converts the string representation of a complex sparse vector to double-precision sparse vector equivalent. + /// A return value indicates whether the conversion succeeded or failed. + /// + /// + /// A string containing a complex vector to convert. + /// + /// + /// An that supplies culture-specific formatting information about value. + /// + /// + /// The parsed value. + /// + /// + /// If the conversion succeeds, the result will contain a complex number equivalent to value. + /// Otherwise the result will be null. + /// + public static bool TryParse(string value, IFormatProvider formatProvider, out SparseVector result) + { + bool ret; + try + { + result = Parse(value, formatProvider); + ret = true; + } + catch (ArgumentNullException) + { + result = null; + ret = false; + } + catch (FormatException) + { + result = null; + ret = false; + } + + return ret; + } + #endregion + + /// + /// Delete, Add or Update the value in NonZeroValues and NonZeroIndices + /// + /// Value real index in array + /// The value to set. + /// This method assume that index is between 0 and Array Size + private void SetValue(int index, Complex value) + { + // Search if "index" already exists in range "0 - complex nonzero values count" + var itemIndex = Array.BinarySearch(_nonZeroIndices, 0, NonZerosCount, index); + + if (itemIndex >= 0) + { + // Item already exist at itemIndex + if (value == Complex.Zero) + { + // Value is zero. Let's delete it from Values and Indices array + for (var i = itemIndex + 1; i < NonZerosCount; i++) + { + _nonZeroValues[i - 1] = _nonZeroValues[i]; + _nonZeroIndices[i - 1] = _nonZeroIndices[i]; + } + + NonZerosCount -= 1; + + // Check if the storage needs to be shrink. This is reasonable to do if + // there are a lot of non-zero elements and storage is two times bigger + if ((NonZerosCount > 1024) && (NonZerosCount < _nonZeroIndices.Length / 2)) + { + Array.Resize(ref _nonZeroValues, NonZerosCount); + Array.Resize(ref _nonZeroIndices, NonZerosCount); + } + } + else + { + _nonZeroValues[itemIndex] = value; + } + } + else + { + if (value == Complex.Zero) + { + return; + } + + itemIndex = ~itemIndex; // Index where to put new value + + // Check if the storage needs to be increased + if ((NonZerosCount == _nonZeroValues.Length) && (NonZerosCount < Count)) + { + // Value and Indices arrays are completely full so we increase the size + var size = Math.Min(_nonZeroValues.Length + GrowthSize(), Count); + Array.Resize(ref _nonZeroValues, size); + Array.Resize(ref _nonZeroIndices, size); + } + + // Move all values (with an position larger than index) in the value array + // to the next position + // move all values (with an position larger than index) in the columIndices + // array to the next position + for (var i = NonZerosCount - 1; i > itemIndex - 1; i--) + { + _nonZeroValues[i + 1] = _nonZeroValues[i]; + _nonZeroIndices[i + 1] = _nonZeroIndices[i]; + } + + // Add the value and the column index + _nonZeroValues[itemIndex] = value; + _nonZeroIndices[itemIndex] = index; + + // increase the number of non-zero numbers by one + NonZerosCount += 1; + } + } + + /// + /// Calculates the amount with which to grow the storage array's if they need to be + /// increased in size. + /// + /// The amount grown. + private int GrowthSize() + { + int delta; + if (_nonZeroValues.Length > 1024) + { + delta = _nonZeroValues.Length / 4; + } + else + { + if (_nonZeroValues.Length > 256) + { + delta = 512; + } + else + { + delta = _nonZeroValues.Length > 64 ? 128 : 32; + } + } + + return delta; + } + + #region System.Object override + + /// + /// Check equality. If this is regular vector, then check by base implementation. If Sparse - use own method. + /// + /// Object to compare + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) + { + var sparseVector = obj as SparseVector; + + if (sparseVector == null) + { + return base.Equals(obj); + } + + // Accept if the argument is the same object as this. + if (ReferenceEquals(this, sparseVector)) + { + return true; + } + + if ((Count != sparseVector.Count) || (NonZerosCount != sparseVector.NonZerosCount)) + { + return false; + } + + // If all else fails, perform element wise comparison. + for (var index = 0; index < NonZerosCount; index++) + { + if (!_nonZeroValues[index].AlmostEqual(sparseVector._nonZeroValues[index]) || (_nonZeroIndices[index] != sparseVector._nonZeroIndices[index])) + { + return false; + } + } + + return true; + } + + /// + /// 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(NonZerosCount, 20); + long hash = 0; + for (var i = 0; i < hashNum; i++) + { +#if SILVERLIGHT + hash ^= Precision.DoubleToInt64Bits(this._nonZeroValues[i].GetHashCode()); +#else + hash ^= BitConverter.DoubleToInt64Bits(_nonZeroValues[i].GetHashCode()); +#endif + } + + return BitConverter.ToInt32(BitConverter.GetBytes(hash), 4); + } + + #endregion + + /// + /// Returns the index of the absolute maximum element. + /// + /// The index of absolute maximum element. + public override int MaximumIndex() + { + throw new NotSupportedException(); + } + + /// + /// Returns the index of the minimum element. + /// + /// The index of minimum element. + public override int MinimumIndex() + { + throw new NotSupportedException(); + } + + #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + /// + /// Subtract two values T-T + /// + /// Left operand value + /// Right operand value + /// Result of subtract + protected sealed override Complex SubtractT(Complex val1, Complex val2) + { + return val1 - val2; + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + /// + /// Divide two values T/T + /// + /// Left operand value + /// Right operand value + /// Result of divide + protected sealed override Complex DivideT(Complex val1, Complex val2) + { + return val1 / val2; + } + + /// + /// Is equal to one? + /// + /// Value to check + /// True if one; otherwise false + protected sealed override bool IsOneT(Complex val1) + { + return Complex.One.AlmostEqual(val1); + } + + /// + /// Take absolute value + /// + /// Source alue + /// True if one; otherwise false + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs b/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs index a4809f79..35709121 100644 --- a/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs +++ b/src/Numerics/LinearAlgebra/Double/DenseMatrix.cs @@ -27,11 +27,8 @@ namespace MathNet.Numerics.LinearAlgebra.Double { using System; - using System.Text; using Distributions; - using Factorization; using Generic; - using Generic.Factorization; using Properties; using Threading; @@ -654,6 +651,15 @@ namespace MathNet.Numerics.LinearAlgebra.Double return matrix; } + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public override Matrix ConjugateTranspose() + { + throw new NotSupportedException("ConjugateTranspose is not supported for real matricies"); + } + #region Simple arithmetic of type T /// /// Add two values T+T @@ -719,5 +725,6 @@ namespace MathNet.Numerics.LinearAlgebra.Double return Math.Abs(val1); } #endregion + } } diff --git a/src/Numerics/LinearAlgebra/Double/DenseVector.cs b/src/Numerics/LinearAlgebra/Double/DenseVector.cs index c3adb759..b95fe041 100644 --- a/src/Numerics/LinearAlgebra/Double/DenseVector.cs +++ b/src/Numerics/LinearAlgebra/Double/DenseVector.cs @@ -30,7 +30,6 @@ namespace MathNet.Numerics.LinearAlgebra.Double using System.Collections.Generic; using System.Globalization; using System.Linq; - using System.Text; using Distributions; using Generic; using NumberTheory; @@ -1499,6 +1498,15 @@ namespace MathNet.Numerics.LinearAlgebra.Double Array.Clear(Data, 0, Data.Length); } + /// + /// Conjugates vector and save result to + /// + /// Target vector + public override void Conjugate(Vector target) + { + throw new NotSupportedException("Conjugate is not supported for real vectors"); + } + #region Simple arithmetic of type T /// /// Add two values T+T diff --git a/src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs b/src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs index b44ed82d..20e94025 100644 --- a/src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs +++ b/src/Numerics/LinearAlgebra/Double/DiagonalMatrix.cs @@ -1671,6 +1671,15 @@ namespace MathNet.Numerics.LinearAlgebra.Double return stringBuilder.ToString(); } + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public override Matrix ConjugateTranspose() + { + throw new NotSupportedException("ConjugateTranspose is not supported for real matricies"); + } + #region Simple arithmetic of type T /// /// Add two values T+T diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs b/src/Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs index 4e724962..f927d00d 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/DenseCholesky.cs @@ -176,6 +176,16 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override double AddT(double val1, double val2) + { + return val1 + val2; + } /// /// Multiply two values T*T diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs b/src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs index ddf133e6..dc949953 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/SparseCholesky.cs @@ -222,6 +222,16 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override double AddT(double val1, double val2) + { + return val1 + val2; + } /// /// Multiply two values T*T diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs b/src/Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs index 8800b903..4728cf6b 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/UserCholesky.cs @@ -256,6 +256,16 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } #region Simple T Mathematics + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected sealed override double AddT(double val1, double val2) + { + return val1 + val2; + } /// /// Multiply two values T*T diff --git a/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs b/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs index 2bb2c4ac..44b804e8 100644 --- a/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs +++ b/src/Numerics/LinearAlgebra/Double/SparseMatrix.cs @@ -1648,6 +1648,15 @@ namespace MathNet.Numerics.LinearAlgebra.Double return stringBuilder.ToString(); } + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public override Matrix ConjugateTranspose() + { + throw new NotSupportedException("ConjugateTranspose is not supported for real matricies"); + } + #region Simple arithmetic of type T /// /// Add two values T+T diff --git a/src/Numerics/LinearAlgebra/Double/SparseVector.cs b/src/Numerics/LinearAlgebra/Double/SparseVector.cs index f5480d72..66e3854e 100644 --- a/src/Numerics/LinearAlgebra/Double/SparseVector.cs +++ b/src/Numerics/LinearAlgebra/Double/SparseVector.cs @@ -1574,7 +1574,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double /// /// /// true if the current object is equal to the parameter; otherwise, false. - /// + /// public override bool Equals(Vector other) { // Reject equality when the argument is null or has a different length. @@ -1618,6 +1618,15 @@ namespace MathNet.Numerics.LinearAlgebra.Double return true; } + /// + /// Conjugates vector and save result to + /// + /// Target vector + public override void Conjugate(Vector target) + { + throw new NotSupportedException("Conjugate is not supported for real vectors"); + } + #region Simple arithmetic of type T /// /// Add two values T+T diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/Cholesky.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/Cholesky.cs index 31e918db..5031cdda 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/Cholesky.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/Cholesky.cs @@ -65,6 +65,17 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization return new LinearAlgebra.Double.Factorization.UserCholesky(matrix as Matrix) as Cholesky; } + if (typeof(T) == typeof(Complex)) + { + var dense = matrix as LinearAlgebra.Complex.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex.Factorization.DenseCholesky(dense) as Cholesky; + } + + return new LinearAlgebra.Complex.Factorization.UserCholesky(matrix as Matrix) as Cholesky; + } + throw new NotImplementedException(); } @@ -108,14 +119,15 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization /// /// Gets the log determinant of the matrix for which the Cholesky matrix was computed. /// - public virtual double DeterminantLn + public virtual T DeterminantLn { get { - var det = 0.0; + var det = default(T); for (var j = 0; j < CholeskyFactor.RowCount; j++) { - det += 2.0 * LogT(CholeskyFactor[j, j]); + // det += 2.0 * CholeskyFactor[j, j].NaturalLogarithm(); + det = AddT(det, MultiplyT(AddT(OneValueT, OneValueT), LogT(CholeskyFactor[j, j]))); } return det; @@ -174,6 +186,14 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization #region Simple arithmetic of type T + /// + /// Add two values T+T + /// + /// Left operand value + /// Right operand value + /// Result of addition + protected abstract T AddT(T val1, T val2); + /// /// Multiply two values T*T /// @@ -187,7 +207,7 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization /// /// A number whose logarithm is to be found /// Natural (base e) logarithm - protected abstract double LogT(T val1); + protected abstract T LogT(T val1); /// /// Gets value of type T equal to one diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/ExtensionMethods.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs similarity index 59% rename from src/Numerics/LinearAlgebra/Double/Factorization/ExtensionMethods.cs rename to src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs index 4c9291c5..abab7755 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/ExtensionMethods.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs @@ -28,10 +28,11 @@ // OTHER DEALINGS IN THE SOFTWARE. // -namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization { + using System; + using System.Numerics; using Generic; - using Generic.Factorization; /// /// Extension methods which return factorizations for the various matrix classes. @@ -43,9 +44,10 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// /// The matrix to factor. /// The Cholesky decomposition object. - public static Cholesky Cholesky(this Matrix matrix) + /// Supported data types are double, single, , and . + public static Cholesky Cholesky(this Matrix matrix) where T : struct, IEquatable, IFormattable { - return Cholesky.Create(matrix); + return Factorization.Cholesky.Create(matrix); } /// @@ -53,9 +55,10 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// /// The matrix to factor. /// The LU decomposition object. - public static LU LU(this Matrix matrix) + /// Supported data types are double, single, , and . + public static LU LU(this Matrix matrix) where T : struct, IEquatable, IFormattable { - return LU.Create(matrix); + return Factorization.LU.Create(matrix); } /// @@ -63,9 +66,10 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// /// The matrix to factor. /// The QR decomposition object. - public static QR QR(this Matrix matrix) + /// Supported data types are double, single, , and . + public static QR QR(this Matrix matrix) where T : struct, IEquatable, IFormattable { - return QR.Create(matrix); + return Factorization.QR.Create(matrix); } /// @@ -73,10 +77,20 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// /// The matrix to factor. /// The QR decomposition object. - public static GramSchmidt GramSchmidt(this Matrix matrix) + /// Supported data types are double, single, , and . + public static QR GramSchmidt(this Matrix matrix) where T : struct, IEquatable, IFormattable { - // NOTE: There is no factory for GramSchmidt. Use constructor of GramSchmidt class. - return new GramSchmidt(matrix); + if (typeof(T) == typeof(double)) + { + return new LinearAlgebra.Double.Factorization.GramSchmidt(matrix as Matrix) as QR; + } + + if (typeof(T) == typeof(Complex)) + { + return new LinearAlgebra.Complex.Factorization.GramSchmidt(matrix as Matrix) as QR; + } + + throw new NotImplementedException(); } /// @@ -85,9 +99,10 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// The matrix to factor. /// Compute the singular U and VT vectors or not. /// The QR decomposition object. - public static Svd Svd(this Matrix matrix, bool computeVectors) + /// Supported data types are double, single, , and . + public static Svd Svd(this Matrix matrix, bool computeVectors) where T : struct, IEquatable, IFormattable { - return Svd.Create(matrix, computeVectors); + return Factorization.Svd.Create(matrix, computeVectors); } } } diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/LU.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/LU.cs index 49f08f62..60f34bb4 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/LU.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/LU.cs @@ -80,6 +80,17 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization return new LinearAlgebra.Double.Factorization.UserLU(matrix as Matrix) as LU; } + if (typeof(T) == typeof(Complex)) + { + var dense = matrix as LinearAlgebra.Complex.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex.Factorization.DenseLU(dense) as LU; + } + + return new LinearAlgebra.Complex.Factorization.UserLU(matrix as Matrix) as LU; + } + throw new NotImplementedException(); } diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs index 27677db8..a973eb4b 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs @@ -80,6 +80,17 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization return new LinearAlgebra.Double.Factorization.UserQR(matrix as Matrix) as QR; } + if (typeof(T) == typeof(Complex)) + { + var dense = matrix as LinearAlgebra.Complex.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex.Factorization.DenseQR(dense) as QR; + } + + return new LinearAlgebra.Complex.Factorization.UserQR(matrix as Matrix) as QR; + } + throw new NotImplementedException(); } diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/Svd.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/Svd.cs index 4231e8c0..c4713a9a 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/Svd.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/Svd.cs @@ -132,6 +132,17 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization return new LinearAlgebra.Double.Factorization.UserSvd(matrix as Matrix, computeVectors) as Svd; } + if (typeof(T) == typeof(Complex)) + { + var dense = matrix as LinearAlgebra.Complex.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex.Factorization.DenseSvd(dense, computeVectors) as Svd; + } + + return new LinearAlgebra.Complex.Factorization.UserSvd(matrix as Matrix, computeVectors) as Svd; + } + throw new NotImplementedException(); } diff --git a/src/Numerics/LinearAlgebra/Generic/Matrix.cs b/src/Numerics/LinearAlgebra/Generic/Matrix.cs index 86c12f39..3d4cd00a 100644 --- a/src/Numerics/LinearAlgebra/Generic/Matrix.cs +++ b/src/Numerics/LinearAlgebra/Generic/Matrix.cs @@ -1487,6 +1487,12 @@ namespace MathNet.Numerics.LinearAlgebra.Generic return ret; } + /// + /// Returns the conjugate transpose of this matrix. + /// + /// The conjugate transpose of this matrix. + public abstract Matrix ConjugateTranspose(); + /// /// Permute the rows of a matrix according to a permutation. /// @@ -1796,9 +1802,6 @@ namespace MathNet.Numerics.LinearAlgebra.Generic var transpose = Transpose(); var aat = this * transpose; - // TODO: Replace with multiple Transpose - //// aat.Gemm(1.0, 0.0, false, true, this, this); - var norm = 0.0; for (var i = 0; i < RowCount; i++) { diff --git a/src/Numerics/LinearAlgebra/Generic/Solvers/IIterator.cs b/src/Numerics/LinearAlgebra/Generic/Solvers/IIterator.cs index 56608bca..a226a43c 100644 --- a/src/Numerics/LinearAlgebra/Generic/Solvers/IIterator.cs +++ b/src/Numerics/LinearAlgebra/Generic/Solvers/IIterator.cs @@ -105,7 +105,7 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Solvers void ResetToPrecalculationState(); #if SILVERLIGHT - IIterator Clone(); + IIterator Clone(); #endif } } diff --git a/src/Numerics/LinearAlgebra/Generic/Solvers/Preconditioners/IPreConditioner.cs b/src/Numerics/LinearAlgebra/Generic/Solvers/Preconditioners/IPreConditioner.cs index e5503df5..70fc8e0c 100644 --- a/src/Numerics/LinearAlgebra/Generic/Solvers/Preconditioners/IPreConditioner.cs +++ b/src/Numerics/LinearAlgebra/Generic/Solvers/Preconditioners/IPreConditioner.cs @@ -65,7 +65,7 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Solvers.Preconditioners /// /// The right hand side vector. /// The left hand side vector. - Vector Approximate(Vector rhs); + Vector Approximate(Vector rhs); /// /// Approximates the solution to the matrix equation Mx = b. diff --git a/src/Numerics/LinearAlgebra/Generic/Solvers/StopCriterium/IIterationStopCriterium.cs b/src/Numerics/LinearAlgebra/Generic/Solvers/StopCriterium/IIterationStopCriterium.cs index 00cda992..cc1c5893 100644 --- a/src/Numerics/LinearAlgebra/Generic/Solvers/StopCriterium/IIterationStopCriterium.cs +++ b/src/Numerics/LinearAlgebra/Generic/Solvers/StopCriterium/IIterationStopCriterium.cs @@ -76,7 +76,7 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Solvers.StopCriterium StopLevel StopLevel { get; } #if SILVERLIGHT - IIterationStopCriterium Clone(); + IIterationStopCriterium Clone(); #endif } } diff --git a/src/Numerics/LinearAlgebra/Generic/Vector.cs b/src/Numerics/LinearAlgebra/Generic/Vector.cs index 1f120118..739076ff 100644 --- a/src/Numerics/LinearAlgebra/Generic/Vector.cs +++ b/src/Numerics/LinearAlgebra/Generic/Vector.cs @@ -1106,6 +1106,23 @@ namespace MathNet.Numerics.LinearAlgebra.Generic #endregion + /// + /// Return vector with conjugate values of the source vector + /// + /// Conjugated vector + public Vector Conjugate() + { + var retrunVector = CreateVector(Count); + Conjugate(retrunVector); + return retrunVector; + } + + /// + /// Conjugates vector and save result to + /// + /// Target vector + public abstract void Conjugate(Vector target); + #region Copying and Conversion /// diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index bcdcdd3a..2f9c896b 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -107,6 +107,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -115,8 +144,8 @@ - + diff --git a/src/Numerics/Precision.cs b/src/Numerics/Precision.cs index 7d9e2c3f..825dcea7 100644 --- a/src/Numerics/Precision.cs +++ b/src/Numerics/Precision.cs @@ -764,18 +764,8 @@ namespace MathNet.Numerics /// 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); + double diff = a.NormOfDifference(b); + return AlmostEqualWithError(a.Norm(), b.Norm(), diff, _defaultDoubleRelativeAccuracy); } /// diff --git a/src/Silverlight/Silverlight.csproj b/src/Silverlight/Silverlight.csproj index ba51877c..0d853513 100644 --- a/src/Silverlight/Silverlight.csproj +++ b/src/Silverlight/Silverlight.csproj @@ -215,6 +215,93 @@ IPrecisionSupport.cs + + LinearAlgebra\Complex\DenseMatrix.cs + + + LinearAlgebra\Complex\DenseVector.cs + + + LinearAlgebra\Complex\DiagonalMatrix.cs + + + LinearAlgebra\Complex\Factorization\DenseCholesky.cs + + + LinearAlgebra\Complex\Factorization\DenseLU.cs + + + LinearAlgebra\Complex\Factorization\DenseQR.cs + + + LinearAlgebra\Complex\Factorization\DenseSvd.cs + + + LinearAlgebra\Complex\Factorization\GramSchmidt.cs + + + LinearAlgebra\Complex\Factorization\UserCholesky.cs + + + LinearAlgebra\Complex\Factorization\UserLU.cs + + + LinearAlgebra\Complex\Factorization\UserQR.cs + + + LinearAlgebra\Complex\Factorization\UserSvd.cs + + + LinearAlgebra\Complex\Solvers\Iterative\BiCgStab.cs + + + LinearAlgebra\Complex\Solvers\Iterative\CompositeSolver.cs + + + LinearAlgebra\Complex\Solvers\Iterative\GpBiCg.cs + + + LinearAlgebra\Complex\Solvers\Iterative\MlkBiCgStab.cs + + + LinearAlgebra\Complex\Solvers\Iterative\TFQMR.cs + + + LinearAlgebra\Complex\Solvers\Iterator.cs + + + LinearAlgebra\Complex\Solvers\Preconditioners\Diagonal.cs + + + LinearAlgebra\Complex\Solvers\Preconditioners\Ilutp.cs + + + LinearAlgebra\Complex\Solvers\Preconditioners\IlutpElementSorter.cs + + + LinearAlgebra\Complex\Solvers\Preconditioners\IncompleteLU.cs + + + LinearAlgebra\Complex\Solvers\Preconditioners\UnitPreconditioner.cs + + + LinearAlgebra\Complex\Solvers\StopCriterium\DivergenceStopCriterium.cs + + + LinearAlgebra\Complex\Solvers\StopCriterium\FailureStopCriterium.cs + + + LinearAlgebra\Complex\Solvers\StopCriterium\IterationCountStopCriterium.cs + + + LinearAlgebra\Complex\Solvers\StopCriterium\ResidualStopCriterium.cs + + + LinearAlgebra\Complex\SparseMatrix.cs + + + LinearAlgebra\Complex\SparseVector.cs + LinearAlgebra\Double\DenseMatrix.cs @@ -236,9 +323,6 @@ LinearAlgebra\Double\Factorization\DenseSvd.cs - - LinearAlgebra\Double\Factorization\ExtensionMethods.cs - LinearAlgebra\Double\Factorization\GramSchmidt.cs @@ -332,6 +416,9 @@ LinearAlgebra\Generic\Factorization\Cholesky.cs + + LinearAlgebra\Generic\Factorization\ExtensionMethods.cs + LinearAlgebra\Generic\Factorization\LU.cs diff --git a/src/UnitTests/LinearAlgebraTests/Complex/DenseMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/DenseMatrixTests.cs new file mode 100644 index 00000000..074b1aa3 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/DenseMatrixTests.cs @@ -0,0 +1,157 @@ +// +// 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.Complex +{ + using System.Collections.Generic; + using System.Numerics; + using LinearAlgebra.Generic; + using MbUnit.Framework; + using LinearAlgebra.Complex; + + public class DenseMatrixTests : MatrixTests + { + protected override Matrix CreateMatrix(int rows, int columns) + { + return new DenseMatrix(rows, columns); + } + + protected override Matrix CreateMatrix(Complex[,] data) + { + return new DenseMatrix(data); + } + + protected override Vector CreateVector(int size) + { + return new DenseVector(size); + } + + protected override Vector CreateVector(Complex[] data) + { + return new DenseVector(data); + } + + [Test] + public void CanCreateMatrixFrom1DArray() + { + Dictionary> testData = new Dictionary> + { + { "Singular3x3", new DenseMatrix(3, 3, new[] { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1), new Complex(2.0, 1), new Complex(2.0, 1) }) }, + { "Square3x3", new DenseMatrix(3, 3, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(-4.4, 1), new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(5.5, 1), new Complex(-3.3, 1), new Complex(2.2, 1), new Complex(6.6, 1) }) }, + { "Square4x4", new DenseMatrix(4, 4, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(1.0, 1), new Complex(-4.4, 1), new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(2.1, 1), new Complex(5.5, 1), new Complex(-3.3, 1), new Complex(2.2, 1), new Complex(6.2, 1), new Complex(6.6, 1), new Complex(-4.4, 1), new Complex(3.3, 1), new Complex(4.3, 1), new Complex(-7.7, 1) }) }, + { "Tall3x2", new DenseMatrix(3, 2, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(-4.4, 1), new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(5.5, 1) }) }, + { "Wide2x3", new DenseMatrix(2, 3, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(-3.3, 1), new Complex(2.2, 1) }) } + }; + + foreach (var name in testData.Keys) + { + Assert.AreEqual(TestMatrices[name], testData[name]); + } + } + + [Test] + public void MatrixFrom1DArrayIsReference() + { + var data = new[] { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1), new Complex(2.0, 1), new Complex(2.0, 1) }; + var matrix = new DenseMatrix(3, 3, data); + matrix[0, 0] = 10.0; + AssertHelpers.AreEqual(10.0, data[0]); + } + + [Test] + public void MatrixFrom2DArrayIsCopy() + { + var matrix = new DenseMatrix(TestData2D["Singular3x3"]); + matrix[0, 0] = 10.0; + AssertHelpers.AreEqual(new Complex(1.0, 1), TestData2D["Singular3x3"][0, 0]); + } + + [Test] + [Row("Singular3x3")] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + public void CanCreateMatrixFrom2DArray(string name) + { + 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++) + { + AssertHelpers.AreEqual(TestData2D[name][i, j], matrix[i, j]); + } + } + } + + [Test] + public void CanCreateMatrixWithUniformValues() + { + var matrix = new DenseMatrix(10, 10, new Complex(10.0, 1)); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], new Complex(10.0, 1)); + } + } + } + + [Test] + public void CanCreateIdentity() + { + var matrix = DenseMatrix.Identity(5); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + if (i == j) + { + AssertHelpers.AreEqual(Complex.One, matrix[i, j]); + } + else + { + AssertHelpers.AreEqual(Complex.Zero, matrix[i, j]); + } + } + } + } + + [Test] + [Row(0)] + [Row(-1)] + [ExpectedArgumentException] + public void IdentityFailsWithZeroOrNegativeOrder(int order) + { + DenseMatrix.Identity(order); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/DenseVectorTest.TextHandling.cs b/src/UnitTests/LinearAlgebraTests/Complex/DenseVectorTest.TextHandling.cs new file mode 100644 index 00000000..67fff10f --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/DenseVectorTest.TextHandling.cs @@ -0,0 +1,100 @@ +// +// 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.Complex +{ + using System; + using System.Globalization; + using LinearAlgebra.Complex; + using MbUnit.Framework; + + [TestFixture] + public class DenseVectorTextHandlingTest + { + [Test] + [Row("2", "(2, 0)")] + [Row("(3)", "(3, 0)")] + [Row("[1,2,3]", "(1, 0),(2, 0),(3, 0)")] + [Row(" [ 1.1 , 2.1 , 3.1 ] ", "(1.1, 0),(2.1, 0),(3.1, 0)")] + [Row(" [ -1.1 , 2.1 , +3.1 ] ", "(-1.1, 0),(2.1, 0),(3.1, 0)")] + [Row(" [1.2,3.4 , 5.6] ", "(1.2, 0),(3.4, 0),(5.6, 0)")] + [Row("[1+1i,2+1i,3+1i]", "(1, 1),(2, 1),(3, 1)")] + [Row(" [ 1.1 + 1i , 2.1+1i , 3.1+1i ] ", "(1.1, 1),(2.1, 1),(3.1, 1)")] + [Row(" [ -1.1 + 1i , 2.1-1i , +3.1+1i ] ", "(-1.1, 1),(2.1, -1),(3.1, 1)")] + [Row(" [1.2+2.3i ,3.4+4.5i , 5.6+ 6.7i] ", "(1.2, 2.3),(3.4, 4.5),(5.6, 6.7)")] + public void CanParseComplexDenseVectorsWithInvariant(string stringToParse, string expectedToString) + { + var formatProvider = CultureInfo.InvariantCulture; + DenseVector vector = DenseVector.Parse(stringToParse, formatProvider); + + Assert.AreEqual(expectedToString, vector.ToString(formatProvider)); + } + + [Test] + [Row(" 1.2 + 1i , 3.4 + 1i , 5.6 + 1i ", "(1.2, 1),(3.4, 1),(5.6, 1)", "en-US")] + [Row(" 1.2 + 1i ; 3.4 + 1i ; 5.6 + 1i ", "(1.2, 1);(3.4, 1);(5.6, 1)", "de-CH")] + [Row(" 1,2 + 1i ; 3,4 + 1i ; 5,6 + 1i ", "(1,2, 1);(3,4, 1);(5,6, 1)", "de-DE")] + public void CanParseComplexDenseVectorsWithCulture(string stringToParse, string expectedToString, string culture) + { + var formatProvider = CultureInfo.GetCultureInfo(culture); + DenseVector vector = DenseVector.Parse(stringToParse, formatProvider); + + Assert.AreEqual(expectedToString, vector.ToString(formatProvider)); + } + + [Test] + [MultipleAsserts] + public void ParseThrowsFormatExceptionIfMissingClosingParen() + { + Assert.Throws(() => DenseVector.Parse("(1")); + Assert.Throws(() => DenseVector.Parse("[1")); + } + + [Test] + [Row(null)] + [Row("")] + [Row(",")] + [Row("1,")] + [Row(",1")] + [Row(",1,2,")] + [Row("1,,2,,3")] + [Row("1e+")] + [Row("1e")] + [Row("()")] + [Row("[ ]")] + public void TryParseReturnsFalseWhenGivenBadValueWithInvariant(string str) + { + DenseVector vector; + var ret = DenseVector.TryParse(str, CultureInfo.InvariantCulture, out vector); + Assert.IsFalse(ret); + Assert.IsNull(vector); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/DenseVectorTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/DenseVectorTests.cs new file mode 100644 index 00000000..ac8b31c1 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/DenseVectorTests.cs @@ -0,0 +1,291 @@ +// +// 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.Complex +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Generic; + using MbUnit.Framework; + + public class DenseVectorTests : VectorTests + { + protected override Vector CreateVector(int size) + { + return new DenseVector(size); + } + + protected override Vector CreateVector(IList data) + { + var vector = new DenseVector(data.Count); + for (var index = 0; index < data.Count; index++) + { + vector[index] = data[index]; + } + + return vector; + } + + [Test] + [MultipleAsserts] + public void CanCreateDenseVectorFromArray() + { + var data = new Complex[Data.Length]; + Array.Copy(Data, data, Data.Length); + var vector = new DenseVector(data); + + Assert.AreSame(data, vector.Data); + for (var i = 0; i < data.Length; i++) + { + AssertHelpers.AreEqual(data[i], vector[i]); + } + + vector[0] = 100.0; + AssertHelpers.AreEqual(100.0, data[0]); + } + + [Test] + [MultipleAsserts] + public void CanCreateDenseVectorFromAnotherDenseVector() + { + var vector = new DenseVector(Data); + var other = new DenseVector(vector); + + Assert.AreNotSame(vector, other); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanCreateDenseVectorFromAnotherVector() + { + var vector = (Vector)new DenseVector(Data); + var other = new DenseVector(vector); + + Assert.AreNotSame(vector, other); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanCreateDenseVectorFromUserDefinedVector() + { + var vector = new UserDefinedVector(Data); + var other = new DenseVector(vector); + + for (var i = 0; i < Data.Length; i++) + { + Assert.AreEqual(vector[i], other[i]); + } + } + + [Test] + public void CanCreateDenseVectorWithConstantValues() + { + var vector = new DenseVector(5, 5); + Assert.ForAll(vector, value => value == 5); + } + + [Test] + [MultipleAsserts] + public void CanCreateDenseMatrix() + { + var vector = new DenseVector(3); + var matrix = vector.CreateMatrix(2, 3); + Assert.AreEqual(2, matrix.RowCount); + Assert.AreEqual(3, matrix.ColumnCount); + } + + + [Test] + [MultipleAsserts] + public void CanConvertDenseVectorToArray() + { + var vector = new DenseVector(Data); + var array = (Complex[])vector; + Assert.IsInstanceOfType(typeof(Complex[]), array); + Assert.AreSame(vector.Data, array); + Assert.AreElementsEqual(vector, array); + } + + [Test] + [MultipleAsserts] + public void CanConvertArrayToDenseVector() + { + var array = new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1), new Complex(4, 1) }; + var vector = (DenseVector)array; + Assert.IsInstanceOfType(typeof(DenseVector), vector); + Assert.AreElementsEqual(array, array); + } + + [Test] + public void CanCallUnaryPlusOperatorOnDenseVector() + { + var vector = new DenseVector(Data); + var other = +vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddTwoDenseVectorsUsingOperator() + { + var vector = new DenseVector(Data); + var other = new DenseVector(Data); + var result = vector + other; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(Data[i] * 2.0, result[i]); + } + } + + [Test] + public void CanCallUnaryNegationOperatorOnDenseVector() + { + var vector = new DenseVector(Data); + var other = -vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(-Data[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractTwoDenseVectorsUsingOperator() + { + var vector = new DenseVector(Data); + var other = new DenseVector(Data); + var result = vector - other; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(0.0, result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanMultiplyDenseVectorByComplexUsingOperators() + { + var vector = new DenseVector(Data); + vector = vector * new Complex(2.0, 1); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + + vector = vector * 1.0; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + + vector = new DenseVector(Data); + vector = new Complex(2.0, 1) * vector; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + + vector = 1.0 * vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanDivideDenseVectorByComplexUsingOperators() + { + var vector = new DenseVector(Data); + vector = vector / new Complex(2.0, 1); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / new Complex(2.0, 1), vector[i]); + } + + vector = vector / 1.0; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / new Complex(2.0, 1), vector[i]); + } + } + + [Test] + public void CanCalculateOuterProductForDenseVector() + { + var vector1 = CreateVector(Data); + var vector2 = CreateVector(Data); + Matrix m = Vector.OuterProduct(vector1, vector2); + for (var i = 0; i < vector1.Count; i++) + { + for (var j = 0; j < vector2.Count; j++) + { + AssertHelpers.AreEqual(m[i, j], vector1[i] * vector2[j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void OuterProducForDenseVectortWithFirstParameterNullShouldThrowException() + { + DenseVector vector1 = null; + var vector2 = CreateVector(Data); + Vector.OuterProduct(vector1, vector2); + } + + [Test] + [ExpectedArgumentNullException] + public void OuterProductForDenseVectorWithSecondParameterNullShouldThrowException() + { + var vector1 = CreateVector(Data); + DenseVector vector2 = null; + Vector.OuterProduct(vector1, vector2); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/DiagonalMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/DiagonalMatrixTests.cs new file mode 100644 index 00000000..156ba7da --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/DiagonalMatrixTests.cs @@ -0,0 +1,452 @@ +// +// 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.Complex +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Numerics; + using LinearAlgebra.Generic; + using MbUnit.Framework; + using LinearAlgebra.Complex; + + public class DiagonalMatrixTests : MatrixTests + { + [SetUp] + public override void SetupMatrices() + { + TestData2D = new Dictionary + { + { "Singular3x3", new [,] { { new Complex(1.0, 1), Complex.Zero, Complex.Zero }, { Complex.Zero, Complex.Zero, Complex.Zero }, { Complex.Zero, Complex.Zero, new Complex(3.0, 1) } } }, + { "Square3x3", new [,] { { new Complex(-1.1, 1), Complex.Zero, Complex.Zero }, { Complex.Zero, new Complex(1.1, 1), Complex.Zero }, { Complex.Zero, Complex.Zero, new Complex(6.6, 1) } } }, + { "Square4x4", new [,] { { new Complex(-1.1, 1), Complex.Zero, Complex.Zero, Complex.Zero }, { Complex.Zero, new Complex(1.1, 1), Complex.Zero, Complex.Zero }, { Complex.Zero, Complex.Zero, new Complex(6.2, 1), Complex.Zero}, { Complex.Zero, Complex.Zero, Complex.Zero, new Complex(-7.7, 1) } } }, + { "Singular4x4", new [,] { { new Complex(-1.1, 1), Complex.Zero, Complex.Zero, Complex.Zero }, { Complex.Zero, new Complex(-2.2, 1), Complex.Zero, Complex.Zero }, { Complex.Zero, Complex.Zero, Complex.Zero, Complex.Zero}, { Complex.Zero, Complex.Zero, Complex.Zero, new Complex(-4.4, 1) } } }, + { "Tall3x2", new [,] { { new Complex(-1.1, 1), Complex.Zero }, { Complex.Zero, new Complex(1.1, 1) }, { Complex.Zero, Complex.Zero } } }, + { "Wide2x3", new [,] { { new Complex(-1.1, 1), Complex.Zero, Complex.Zero }, { Complex.Zero, new Complex(1.1, 1), Complex.Zero } } } + }; + + 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(Complex[,] data) + { + return new DiagonalMatrix(data); + } + + protected override Vector CreateVector(int size) + { + return new SparseVector(size); + } + + protected override Vector CreateVector(Complex[] data) + { + return new SparseVector(data); + } + + [Test] + public void CanCreateMatrixFromDiagonalArray() + { + var testData = new Dictionary> + { + { "Singular3x3", new DiagonalMatrix(3, 3, new[] { new Complex(1.0, 1), Complex.Zero, new Complex(3.0, 1) }) }, + { "Square3x3", new DiagonalMatrix(4, 4, new[] { new Complex(-1.1, 1), new Complex(1.1, 1), new Complex(6.6, 1) }) }, + { "Square4x4", new DiagonalMatrix(4, 4, new[] { new Complex(-1.1, 1), new Complex(1.1, 1), new Complex(6.2, 1), new Complex(-7.7, 1) }) }, + { "Tall3x2", new DiagonalMatrix(3, 2, new[] { new Complex(-1.1, 1), new Complex(1.1, 1) }) }, + { "Wide2x3", new DiagonalMatrix(2, 3, new[] { new Complex(-1.1, 1), new Complex(1.1, 1) }) }, + }; + foreach (var name in testData.Keys) + { + Assert.AreEqual(TestMatrices[name], testData[name]); + } + } + + [Test] + public void MatrixFrom1DArrayIsReference() + { + var data = new[] { new Complex(1.0, 1), new Complex(2.0, 1), new Complex(3.0, 1), new Complex(4.0, 1), new Complex(5.0, 1) }; + 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]).Real, result[i, i].Real); + Assert.AreEqual((data[i, i] / other[i, i]).Imaginary, result[i, i].Imaginary); + } + + result = data.PointwiseDivide(other); + for (var i = 0; i < min; i++) + { + Assert.AreEqual((data[i, i] / other[i, i]).Real, result[i, i].Real); + Assert.AreEqual((data[i, i] / other[i, i]).Imaginary, result[i, i].Imaginary); + } + } + } + + 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/Complex/Factorization/CholeskyTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/CholeskyTests.cs new file mode 100644 index 00000000..344a5aa5 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/CholeskyTests.cs @@ -0,0 +1,305 @@ +// +// 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.Complex.Factorization +{ + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex; + + public class CholeskyTests + { + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = DenseMatrix.Identity(order); + var factorC = I.Cholesky(); + + Assert.AreEqual(I.RowCount, factorC.Factor.RowCount); + Assert.AreEqual(I.ColumnCount, factorC.Factor.ColumnCount); + + for (var i = 0; i < factorC.Factor.RowCount; i++) + { + for (var j = 0; j < factorC.Factor.ColumnCount; j++) + { + Assert.AreEqual(i == j ? 1.0 : 0.0, factorC.Factor[i, j]); + } + } + } + + [Test] + [ExpectedArgumentException] + public void CholeskyFailsWithDiagonalNonPositiveDefiniteMatrix() + { + var I = DenseMatrix.Identity(10); + I[3, 3] = -4.0; + I.Cholesky(); + } + + [Test] + [Row(3,5)] + [Row(5,3)] + [ExpectedArgumentException] + public void CholeskyFailsWithNonSquareMatrix(int row, int col) + { + var I = new DenseMatrix(row, col); + I.Cholesky(); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = DenseMatrix.Identity(order); + var factorC = I.Cholesky(); + Assert.AreEqual(1.0, factorC.Determinant); + Assert.AreEqual(0.0, factorC.DeterminantLn); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixX = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var chol = matrixX.Cholesky(); + var factorC = chol.Factor; + + // Make sure the Cholesky factor has the right dimensions. + Assert.AreEqual(order, factorC.RowCount); + Assert.AreEqual(order, factorC.ColumnCount); + + // Make sure the Cholesky factor is lower triangular. + for (var i = 0; i < factorC.RowCount; i++) + { + for (var j = i+1; j < factorC.ColumnCount; j++) + { + Assert.AreEqual(0.0, factorC[i, j]); + } + } + + // Make sure the cholesky factor times it's transpose is the original matrix. + var matrixXfromC = factorC * factorC.ConjugateTranspose(); + for (var i = 0; i < matrixXfromC.RowCount; i++) + { + for (var j = 0; j < matrixXfromC.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixX[i,j].Real, matrixXfromC[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixX[i, j].Imaginary, matrixXfromC[i, j].Imaginary, 1.0e-9); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var b = MatrixLoader.GenerateRandomDenseVector(order); + var x = chol.Solve(b); + + Assert.AreEqual(b.Count, x.Count); + + var bReconstruct = matrixA * x; + + // Check the reconstruction. + for (var i = 0; i < order; i++) + { + Assert.AreApproximatelyEqual(b[i].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(b[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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,1)] + [Row(2,4)] + [Row(5,8)] + [Row(10,3)] + [Row(50,10)] + [Row(100,100)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int row, int col) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(row); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(row, col); + var matrixX = chol.Solve(matrixB); + + Assert.AreEqual(matrixB.RowCount, matrixX.RowCount); + 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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var b = MatrixLoader.GenerateRandomDenseVector(order); + var bCopy = b.Clone(); + var x = new DenseVector(order); + chol.Solve(b, x); + + Assert.AreEqual(b.Count, x.Count); + + var bReconstruct = matrixA * x; + + // Check the reconstruction. + for (var i = 0; i < order; i++) + { + Assert.AreApproximatelyEqual(b[i].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(b[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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 < order; i++) + { + Assert.AreEqual(bCopy[i], b[i]); + } + } + + [Test] + [Row(1, 1)] + [Row(2, 4)] + [Row(5, 8)] + [Row(10, 3)] + [Row(50, 10)] + [Row(100, 100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixWhenResultMatrixGiven(int row, int col) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(row); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(row, col); + var matrixBCopy = matrixB.Clone(); + var matrixX = new DenseMatrix(row, col); + chol.Solve(matrixB, matrixX); + + Assert.AreEqual(matrixB.RowCount, matrixX.RowCount); + 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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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/Complex/Factorization/GramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/GramSchmidtTests.cs new file mode 100644 index 00000000..5ef1872b --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/GramSchmidtTests.cs @@ -0,0 +1,356 @@ +// +// 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.Complex.Factorization +{ + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex.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].Real, matrixQfromR[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixA[i, j].Imaginary, matrixQfromR[i, j].Imaginary, 1.0e-9); + } + } + + // Make sure the Q is unitary --> (Q*)x(Q) = I + var matrixQсtQ = factorGramSchmidt.Q.ConjugateTranspose() * factorGramSchmidt.Q; + for (var i = 0; i < matrixQсtQ.RowCount; i++) + { + for (var j = 0; j < matrixQсtQ.ColumnCount; j++) + { + if (i == j) + { + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Real, 1.0, 1.0e-9); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0, 1.0e-9); + } + else + { + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Real, 0.0, 1.0e-9); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0, 1.0e-9); + } + } + } + + } + + [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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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/Complex/Factorization/LUTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/LUTests.cs new file mode 100644 index 00000000..105884eb --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/LUTests.cs @@ -0,0 +1,371 @@ +// +// 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.Complex.Factorization +{ + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex; + + public class LUTests + { + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var matrixI = DenseMatrix.Identity(order); + var factorLU = matrixI.LU(); + + // Check lower triangular part. + var matrixL = factorLU.L; + Assert.AreEqual(matrixI.RowCount, matrixL.RowCount); + Assert.AreEqual(matrixI.ColumnCount, matrixL.ColumnCount); + for (var i = 0; i < matrixL.RowCount; i++) + { + for (var j = 0; j < matrixL.ColumnCount; j++) + { + Assert.AreEqual(i == j ? Complex.One : Complex.Zero, matrixL[i, j]); + } + } + + // Check upper triangular part. + var matrixU = factorLU.U; + Assert.AreEqual(matrixI.RowCount, matrixU.RowCount); + Assert.AreEqual(matrixI.ColumnCount, matrixU.ColumnCount); + for (var i = 0; i < matrixU.RowCount; i++) + { + for (var j = 0; j < matrixU.ColumnCount; j++) + { + Assert.AreEqual(i == j ? Complex.One : Complex.Zero, matrixU[i, j]); + } + } + } + + [Test] + [Row(3,5)] + [Row(5,3)] + [ExpectedArgumentException] + public void LUFailsWithNonSquareMatrix(int row, int col) + { + var I = new DenseMatrix(row, col); + I.LU(); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = DenseMatrix.Identity(order); + var lu = I.LU(); + Assert.AreEqual(Complex.One, lu.Determinant); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixX = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorLU = matrixX.LU(); + var matrixL = factorLU.L; + var matrixU = factorLU.U; + + // Make sure the factors have the right dimensions. + Assert.AreEqual(order, matrixL.RowCount); + Assert.AreEqual(order, matrixL.ColumnCount); + Assert.AreEqual(order, matrixU.RowCount); + Assert.AreEqual(order, matrixU.ColumnCount); + + // Make sure the L factor is lower triangular. + for (var i = 0; i < matrixL.RowCount; i++) + { + Assert.AreEqual(1.0, matrixL[i, i]); + for (var j = i+1; j < matrixL.ColumnCount; j++) + { + Assert.AreEqual(0.0, matrixL[i, j]); + } + } + + // Make sure the U factor is upper triangular. + for (var i = 0; i < matrixL.RowCount; i++) + { + for (var j = 0; j < i; j++) + { + Assert.AreEqual(0.0, matrixU[i, j]); + } + } + + // Make sure the LU factor times it's transpose is the original matrix. + var matrixXfromLU = matrixL * matrixU; + var permutationInverse = factorLU.P.Inverse(); + matrixXfromLU.PermuteRows(permutationInverse); + for (var i = 0; i < matrixXfromLU.RowCount; i++) + { + for (var j = 0; j < matrixXfromLU.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixX[i, j].Real, matrixXfromLU[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixX[i, j].Imaginary, matrixXfromLU[i, j].Imaginary, 1.0e-9); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorLU = matrixA.LU(); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var resultx = factorLU.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorLU = matrixA.LU(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixX = factorLU.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorLU = matrixA.LU(); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new DenseVector(order); + factorLU.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorLU = matrixA.LU(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new DenseMatrix(order, order); + factorLU.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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]); + } + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanInverse(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorLU = matrixA.LU(); + + var matrixAInverse = factorLU.Inverse(); + + // The inverse dimension is equal A + Assert.AreEqual(matrixAInverse.RowCount, matrixAInverse.RowCount); + Assert.AreEqual(matrixAInverse.ColumnCount, matrixAInverse.ColumnCount); + + var matrixIdentity = matrixA * matrixAInverse; + + // 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]); + } + } + + // Check if multiplication of A and AI produced identity matrix. + for (var i = 0; i < matrixIdentity.RowCount; i++) + { + Assert.AreApproximatelyEqual(matrixIdentity[i, i].Real, Complex.One.Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixIdentity[i, i].Imaginary, Complex.One.Imaginary, 1.0e-9); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/QRTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/QRTests.cs new file mode 100644 index 00000000..c61a0d05 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/QRTests.cs @@ -0,0 +1,343 @@ +// +// 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.Complex.Factorization +{ + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Factorization; + + public class QRTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new DenseQR(null); + } + + [Test] + [ExpectedArgumentException] + public void WideMatrixThrowsInvalidMatrixOperationException() + { + new DenseQR(new DenseMatrix(3, 4)); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = DenseMatrix.Identity(order); + var factorQR = I.QR(); + + Assert.AreEqual(I.RowCount, factorQR.R.RowCount); + Assert.AreEqual(I.ColumnCount, factorQR.R.ColumnCount); + + for (var i = 0; i < factorQR.R.RowCount; i++) + { + for (var j = 0; j < factorQR.R.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(-Complex.One, factorQR.R[i, j]); + } + else + { + Assert.AreEqual(Complex.Zero, factorQR.R[i, j]); + } + } + } + } + + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = DenseMatrix.Identity(order); + var factorQR = I.QR(); + Assert.AreEqual(Complex.One, factorQR.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.GenerateRandomDenseMatrix(row, column); + var factorQR = matrixA.QR(); + + // Make sure the R has the right dimensions. + Assert.AreEqual(row, factorQR.R.RowCount); + Assert.AreEqual(column, factorQR.R.ColumnCount); + + // Make sure the Q has the right dimensions. + Assert.AreEqual(row, factorQR.Q.RowCount); + Assert.AreEqual(row, factorQR.Q.ColumnCount); + + // Make sure the R factor is upper triangular. + for (var i = 0; i < factorQR.R.RowCount; i++) + { + for (var j = 0; j < factorQR.R.ColumnCount; j++) + { + if (i > j) + { + Assert.AreEqual(Complex.Zero, factorQR.R[i, j]); + } + } + } + + // Make sure the Q*R is the original matrix. + var matrixQfromR = factorQR.Q * factorQR.R; + for (var i = 0; i < matrixQfromR.RowCount; i++) + { + for (var j = 0; j < matrixQfromR.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixA[i, j].Real, matrixQfromR[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixA[i, j].Imaginary, matrixQfromR[i, j].Imaginary, 1.0e-9); + } + } + + // Make sure the Q is unitary --> (Q*)x(Q) = I + var matrixQсtQ = factorQR.Q.ConjugateTranspose() * factorQR.Q; + for (var i = 0; i < matrixQсtQ.RowCount; i++) + { + for (var j = 0; j < matrixQсtQ.ColumnCount; j++) + { + if (i == j) + { + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Real, 1.0, 1.0e-9); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0, 1.0e-9); + } + else + { + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Real, 0.0, 1.0e-9); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0, 1.0e-9); + } + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorQR = matrixA.QR(); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var resultx = factorQR.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorQR = matrixA.QR(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixX = factorQR.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorQR = matrixA.QR(); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new DenseVector(order); + factorQR.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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.GenerateRandomDenseMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorQR = matrixA.QR(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new DenseMatrix(order, order); + factorQR.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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/Complex/Factorization/SvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/SvdTests.cs new file mode 100644 index 00000000..4cc89782 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/SvdTests.cs @@ -0,0 +1,376 @@ +// +// 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.Complex.Factorization +{ + using System; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Factorization; + + public class SvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new DenseSvd(null, true); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = DenseMatrix.Identity(order); + var factorSvd = I.Svd(true); + + Assert.AreEqual(I.RowCount, factorSvd.U().RowCount); + Assert.AreEqual(I.RowCount, factorSvd.U().ColumnCount); + + Assert.AreEqual(I.ColumnCount, factorSvd.VT().RowCount); + Assert.AreEqual(I.ColumnCount, factorSvd.VT().ColumnCount); + + Assert.AreEqual(I.RowCount, factorSvd.W().RowCount); + Assert.AreEqual(I.ColumnCount, factorSvd.W().ColumnCount); + + for (var i = 0; i < factorSvd.W().RowCount; i++) + { + for (var j = 0; j < factorSvd.W().ColumnCount; j++) + { + Assert.AreEqual(i == j ? 1.0 : 0.0, factorSvd.W()[i, j]); + } + } + } + + [Test] + [Row(1,1)] + [Row(2,2)] + [Row(5,5)] + [Row(10,6)] + [Row(48,52)] + [Row(100,93)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); + var factorSvd = matrixA.Svd(true); + + // Make sure the U has the right dimensions. + Assert.AreEqual(row, factorSvd.U().RowCount); + Assert.AreEqual(row, factorSvd.U().ColumnCount); + + // Make sure the VT has the right dimensions. + Assert.AreEqual(column, factorSvd.VT().RowCount); + Assert.AreEqual(column, factorSvd.VT().ColumnCount); + + // Make sure the W has the right dimensions. + Assert.AreEqual(row, factorSvd.W().RowCount); + Assert.AreEqual(column, factorSvd.W().ColumnCount); + + // Make sure the U*W*VT is the original matrix. + var matrix = factorSvd.U() * factorSvd.W() * factorSvd.VT(); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixA[i, j].Real, matrix[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixA[i, j].Imaginary, matrix[i, j].Imaginary, 1.0e-9); + } + } + } + + [Test] + [Row(10, 8)] + [Row(48, 52)] + [Row(100, 93)] + [MultipleAsserts] + public void CheckRankOfNonSquare(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); + var factorSvd = matrixA.Svd(true); + + var mn = Math.Min(row, column); + Assert.AreEqual(factorSvd.Rank, mn); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(9)] + [Row(50)] + [Row(90)] + [MultipleAsserts] + public void CheckRankSquare(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorSvd = matrixA.Svd(true); + + if (factorSvd.Determinant != 0) + { + Assert.AreEqual(factorSvd.Rank, order); + } + else + { + Assert.AreEqual(factorSvd.Rank, order - 1); + } + } + + [Test] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CheckRankOfSquareSingular(int order) + { + var matrixA = new DenseMatrix(order, order); + matrixA[0, 0] = 1; + matrixA[order - 1, order - 1] = 1; + for (var i = 1; i < order - 1; i++) + { + matrixA[i, i - 1] = 1; + matrixA[i, i + 1] = 1; + matrixA[i - 1, i] = 1; + matrixA[i + 1, i] = 1; + } + var factorSvd = matrixA.Svd(true); + + Assert.AreEqual(factorSvd.Determinant, 0); + Assert.AreEqual(factorSvd.Rank, order - 1); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CannotSolveMatrixIfVectorsNotComputed() + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(10, 10); + var factorSvd = matrixA.Svd(false); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(10, 10); + factorSvd.Solve(matrixB); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CannotSolveVectorIfVectorsNotComputed() + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(10, 10); + var factorSvd = matrixA.Svd(false); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(10); + factorSvd.Solve(vectorb); + } + + [Test] + [Row(1, 1)] + [Row(2, 2)] + [Row(5, 5)] + [Row(9, 10)] + [Row(50, 50)] + [Row(90, 100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(row); + var resultx = factorSvd.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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, 1)] + [Row(4, 4)] + [Row(7, 8)] + [Row(10, 10)] + [Row(45, 50)] + [Row(80, 100)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int row, int count) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, count); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(row, count); + var matrixX = factorSvd.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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, 1)] + [Row(2, 2)] + [Row(5, 5)] + [Row(9, 10)] + [Row(50, 50)] + [Row(90, 100)] + [MultipleAsserts] + public void CanSolveForRandomVectorWhenResultVectorGiven(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + var vectorb = MatrixLoader.GenerateRandomDenseVector(row); + var vectorbCopy = vectorb.Clone(); + var resultx = new DenseVector(column); + factorSvd.Solve(vectorb,resultx); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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, 1)] + [Row(4, 4)] + [Row(7, 8)] + [Row(10, 10)] + [Row(45, 50)] + [Row(80, 100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixWhenResultMatrixGiven(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(row, column); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new DenseMatrix(column, column); + factorSvd.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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/Complex/Factorization/UserCholeskyTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserCholeskyTests.cs new file mode 100644 index 00000000..4b6b8a8a --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserCholeskyTests.cs @@ -0,0 +1,304 @@ +// +// 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.Complex.Factorization +{ + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + + public class UserCholeskyTests + { + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorC = I.Cholesky(); + + Assert.AreEqual(I.RowCount, factorC.Factor.RowCount); + Assert.AreEqual(I.ColumnCount, factorC.Factor.ColumnCount); + + for (var i = 0; i < factorC.Factor.RowCount; i++) + { + for (var j = 0; j < factorC.Factor.ColumnCount; j++) + { + Assert.AreEqual(i == j ? 1.0 : 0.0, factorC.Factor[i, j]); + } + } + } + + [Test] + [ExpectedArgumentException] + public void CholeskyFailsWithDiagonalNonPositiveDefiniteMatrix() + { + var I = UserDefinedMatrix.Identity(10); + I[3, 3] = -4.0; + I.Cholesky(); + } + + [Test] + [Row(3,5)] + [Row(5,3)] + [ExpectedArgumentException] + public void CholeskyFailsWithNonSquareMatrix(int row, int col) + { + var I = new UserDefinedMatrix(row, col); + I.Cholesky(); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorC = I.Cholesky(); + Assert.AreEqual(1.0, factorC.Determinant); + Assert.AreEqual(0.0, factorC.DeterminantLn); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixX = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var chol = matrixX.Cholesky(); + var factorC = chol.Factor; + + // Make sure the Cholesky factor has the right dimensions. + Assert.AreEqual(order, factorC.RowCount); + Assert.AreEqual(order, factorC.ColumnCount); + + // Make sure the Cholesky factor is lower triangular. + for (var i = 0; i < factorC.RowCount; i++) + { + for (var j = i+1; j < factorC.ColumnCount; j++) + { + Assert.AreEqual(0.0, factorC[i, j]); + } + } + + // Make sure the cholesky factor times it's conjugate transpose is the original matrix. + var matrixXfromC = factorC * factorC.ConjugateTranspose(); + for (var i = 0; i < matrixXfromC.RowCount; i++) + { + for (var j = 0; j < matrixXfromC.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixX[i,j].Real, matrixXfromC[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixX[i, j].Imaginary, matrixXfromC[i, j].Imaginary, 1.0e-9); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var b = MatrixLoader.GenerateRandomUserDefinedVector(order); + var x = chol.Solve(b); + + Assert.AreEqual(b.Count, x.Count); + + var bReconstruct = matrixA * x; + + // Check the reconstruction. + for (var i = 0; i < order; i++) + { + Assert.AreApproximatelyEqual(b[i].Real, bReconstruct[i].Real, 1.0e-11); + Assert.AreApproximatelyEqual(b[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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,1)] + [Row(2,4)] + [Row(5,8)] + [Row(10,3)] + [Row(50,10)] + [Row(100,100)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int row, int col) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(row); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(row, col); + var matrixX = chol.Solve(matrixB); + + Assert.AreEqual(matrixB.RowCount, matrixX.RowCount); + 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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var b = MatrixLoader.GenerateRandomUserDefinedVector(order); + var bCopy = b.Clone(); + var x = new UserDefinedVector(order); + chol.Solve(b, x); + + Assert.AreEqual(b.Count, x.Count); + + var bReconstruct = matrixA * x; + + // Check the reconstruction. + for (var i = 0; i < order; i++) + { + Assert.AreApproximatelyEqual(b[i].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(b[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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 < order; i++) + { + Assert.AreEqual(bCopy[i], b[i]); + } + } + + [Test] + [Row(1, 1)] + [Row(2, 4)] + [Row(5, 8)] + [Row(10, 3)] + [Row(50, 10)] + [Row(100, 100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixWhenResultMatrixGiven(int row, int col) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(row); + var matrixACopy = matrixA.Clone(); + var chol = matrixA.Cholesky(); + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(row, col); + var matrixBCopy = matrixB.Clone(); + var matrixX = new UserDefinedMatrix(row, col); + chol.Solve(matrixB, matrixX); + + Assert.AreEqual(matrixB.RowCount, matrixX.RowCount); + 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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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/Complex/Factorization/UserLUTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserLUTests.cs new file mode 100644 index 00000000..ddb8094f --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserLUTests.cs @@ -0,0 +1,370 @@ +// +// 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.Complex.Factorization +{ + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + + public class UserLUTests + { + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var matrixI = UserDefinedMatrix.Identity(order); + var factorLU = matrixI.LU(); + + // Check lower triangular part. + var matrixL = factorLU.L; + Assert.AreEqual(matrixI.RowCount, matrixL.RowCount); + Assert.AreEqual(matrixI.ColumnCount, matrixL.ColumnCount); + for (var i = 0; i < matrixL.RowCount; i++) + { + for (var j = 0; j < matrixL.ColumnCount; j++) + { + Assert.AreEqual(i == j ? Complex.One : Complex.Zero, matrixL[i, j]); + } + } + + // Check upper triangular part. + var matrixU = factorLU.U; + Assert.AreEqual(matrixI.RowCount, matrixU.RowCount); + Assert.AreEqual(matrixI.ColumnCount, matrixU.ColumnCount); + for (var i = 0; i < matrixU.RowCount; i++) + { + for (var j = 0; j < matrixU.ColumnCount; j++) + { + Assert.AreEqual(i == j ? Complex.One : Complex.Zero, matrixU[i, j]); + } + } + } + + [Test] + [Row(3,5)] + [Row(5,3)] + [ExpectedArgumentException] + public void LUFailsWithNonSquareMatrix(int row, int col) + { + var I = new UserDefinedMatrix(row, col); + I.LU(); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = UserDefinedMatrix.Identity(order); + var lu = I.LU(); + Assert.AreEqual(Complex.One, lu.Determinant); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixX = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var factorLU = matrixX.LU(); + var matrixL = factorLU.L; + var matrixU = factorLU.U; + + // Make sure the factors have the right dimensions. + Assert.AreEqual(order, matrixL.RowCount); + Assert.AreEqual(order, matrixL.ColumnCount); + Assert.AreEqual(order, matrixU.RowCount); + Assert.AreEqual(order, matrixU.ColumnCount); + + // Make sure the L factor is lower triangular. + for (var i = 0; i < matrixL.RowCount; i++) + { + Assert.AreEqual(1.0, matrixL[i, i]); + for (var j = i+1; j < matrixL.ColumnCount; j++) + { + Assert.AreEqual(Complex.Zero, matrixL[i, j]); + } + } + + // Make sure the U factor is upper triangular. + for (var i = 0; i < matrixL.RowCount; i++) + { + for (var j = 0; j < i; j++) + { + Assert.AreEqual(Complex.Zero, matrixU[i, j]); + } + } + + // Make sure the LU factor times it's transpose is the original matrix. + var matrixXfromLU = matrixL * matrixU; + var permutationInverse = factorLU.P.Inverse(); + matrixXfromLU.PermuteRows(permutationInverse); + for (var i = 0; i < matrixXfromLU.RowCount; i++) + { + for (var j = 0; j < matrixXfromLU.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixX[i, j].Real, matrixXfromLU[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixX[i, j].Imaginary, matrixXfromLU[i, j].Imaginary, 1.0e-9); + } + } + } + + [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 factorLU = matrixA.LU(); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorLU.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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 factorLU = matrixA.LU(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorLU.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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 factorLU = matrixA.LU(); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorLU.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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 factorLU = matrixA.LU(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorLU.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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]); + } + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanInverse(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorLU = matrixA.LU(); + + var matrixAInverse = factorLU.Inverse(); + + // The inverse dimension is equal A + Assert.AreEqual(matrixAInverse.RowCount, matrixAInverse.RowCount); + Assert.AreEqual(matrixAInverse.ColumnCount, matrixAInverse.ColumnCount); + + var matrixIdentity = matrixA * matrixAInverse; + + // 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]); + } + } + + // Check if multiplication of A and AI produced identity matrix. + for (var i = 0; i < matrixIdentity.RowCount; i++) + { + Assert.AreApproximatelyEqual(matrixIdentity[i, i].Real, Complex.One.Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixIdentity[i, i].Imaginary, Complex.One.Imaginary, 1.0e-9); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserQRTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserQRTests.cs new file mode 100644 index 00000000..c4b59c15 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserQRTests.cs @@ -0,0 +1,342 @@ +// +// 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.Complex.Factorization +{ + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex.Factorization; + + public class UserQRTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserQR(null); + } + + [Test] + [ExpectedArgumentException] + public void WideMatrixThrowsInvalidMatrixOperationException() + { + new UserQR(new UserDefinedMatrix(3, 4)); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorQR = I.QR(); + + Assert.AreEqual(I.RowCount, factorQR.R.RowCount); + Assert.AreEqual(I.ColumnCount, factorQR.R.ColumnCount); + + for (var i = 0; i < factorQR.R.RowCount; i++) + { + for (var j = 0; j < factorQR.R.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(-1.0, factorQR.R[i, j]); + } + else + { + Assert.AreEqual(0.0, factorQR.R[i, j]); + } + } + } + } + + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorQR = I.QR(); + Assert.AreEqual(1.0, factorQR.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 factorQR = matrixA.QR(); + + // Make sure the R has the right dimensions. + Assert.AreEqual(row, factorQR.R.RowCount); + Assert.AreEqual(column, factorQR.R.ColumnCount); + + // Make sure the Q has the right dimensions. + Assert.AreEqual(row, factorQR.Q.RowCount); + Assert.AreEqual(row, factorQR.Q.ColumnCount); + + // Make sure the R factor is upper triangular. + for (var i = 0; i < factorQR.R.RowCount; i++) + { + for (var j = 0; j < factorQR.R.ColumnCount; j++) + { + if (i > j) + { + Assert.AreEqual(0.0, factorQR.R[i, j]); + } + } + } + + // Make sure the Q*R is the original matrix. + var matrixQfromR = factorQR.Q * factorQR.R; + for (var i = 0; i < matrixQfromR.RowCount; i++) + { + for (var j = 0; j < matrixQfromR.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixA[i, j].Real, matrixQfromR[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixA[i, j].Imaginary, matrixQfromR[i, j].Imaginary, 1.0e-9); + } + } + + // Make sure the Q is unitary --> (Q*)x(Q) = I + var matrixQсtQ = factorQR.Q.ConjugateTranspose() * factorQR.Q; + for (var i = 0; i < matrixQсtQ.RowCount; i++) + { + for (var j = 0; j < matrixQсtQ.ColumnCount; j++) + { + if (i == j) + { + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Real, 1.0, 1.0e-9); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0, 1.0e-9); + } + else + { + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Real, 0.0, 1.0e-9); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0, 1.0e-9); + } + } + } + } + + [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 factorQR = matrixA.QR(); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorQR.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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 factorQR = matrixA.QR(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorQR.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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 factorQR = matrixA.QR(); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorQR.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].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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 factorQR = matrixA.QR(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorQR.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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/Complex/Factorization/UserSvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserSvdTests.cs new file mode 100644 index 00000000..8b489d0c --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserSvdTests.cs @@ -0,0 +1,376 @@ +// +// 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.Complex.Factorization +{ + using System; + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex.Factorization; + + public class UserSvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserSvd(null, true); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorSvd = I.Svd(true); + + Assert.AreEqual(I.RowCount, factorSvd.U().RowCount); + Assert.AreEqual(I.RowCount, factorSvd.U().ColumnCount); + + Assert.AreEqual(I.ColumnCount, factorSvd.VT().RowCount); + Assert.AreEqual(I.ColumnCount, factorSvd.VT().ColumnCount); + + Assert.AreEqual(I.RowCount, factorSvd.W().RowCount); + Assert.AreEqual(I.ColumnCount, factorSvd.W().ColumnCount); + + for (var i = 0; i < factorSvd.W().RowCount; i++) + { + for (var j = 0; j < factorSvd.W().ColumnCount; j++) + { + Assert.AreEqual(i == j ? Complex.One : Complex.Zero, factorSvd.W()[i, j]); + } + } + } + + [Test] + [Row(1,1)] + [Row(2,2)] + [Row(5,5)] + [Row(10,6)] + [Row(48,52)] + [Row(100,93)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var factorSvd = matrixA.Svd(true); + + // Make sure the U has the right dimensions. + Assert.AreEqual(row, factorSvd.U().RowCount); + Assert.AreEqual(row, factorSvd.U().ColumnCount); + + // Make sure the VT has the right dimensions. + Assert.AreEqual(column, factorSvd.VT().RowCount); + Assert.AreEqual(column, factorSvd.VT().ColumnCount); + + // Make sure the W has the right dimensions. + Assert.AreEqual(row, factorSvd.W().RowCount); + Assert.AreEqual(column, factorSvd.W().ColumnCount); + + // Make sure the U*W*VT is the original matrix. + var matrix = factorSvd.U() * factorSvd.W() * factorSvd.VT(); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixA[i, j].Real, matrix[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixA[i, j].Imaginary, matrix[i, j].Imaginary, 1.0e-9); + } + } + } + + [Test] + [Row(10, 8)] + [Row(48, 52)] + [Row(100, 93)] + [MultipleAsserts] + public void CheckRankOfNonSquare(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var factorSvd = matrixA.Svd(true); + + var mn = Math.Min(row, column); + Assert.AreEqual(factorSvd.Rank, mn); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(9)] + [Row(50)] + [Row(90)] + [MultipleAsserts] + public void CheckRankSquare(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var factorSvd = matrixA.Svd(true); + + if (factorSvd.Determinant != 0) + { + Assert.AreEqual(factorSvd.Rank, order); + } + else + { + Assert.AreEqual(factorSvd.Rank, order - 1); + } + } + + [Test] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CheckRankOfSquareSingular(int order) + { + var matrixA = new UserDefinedMatrix(order, order); + matrixA[0, 0] = 1; + matrixA[order - 1, order - 1] = 1; + for (var i = 1; i < order - 1; i++) + { + matrixA[i, i - 1] = 1; + matrixA[i, i + 1] = 1; + matrixA[i - 1, i] = 1; + matrixA[i + 1, i] = 1; + } + var factorSvd = matrixA.Svd(true); + + Assert.AreEqual(factorSvd.Determinant, 0); + Assert.AreEqual(factorSvd.Rank, order - 1); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CannotSolveMatrixIfVectorsNotComputed() + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(10, 10); + var factorSvd = matrixA.Svd(false); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(10, 10); + factorSvd.Solve(matrixB); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CannotSolveVectorIfVectorsNotComputed() + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(10, 10); + var factorSvd = matrixA.Svd(false); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(10); + factorSvd.Solve(vectorb); + } + + [Test] + [Row(1, 1)] + [Row(2, 2)] + [Row(5, 5)] + [Row(9, 10)] + [Row(50, 50)] + [Row(90, 100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(row); + var resultx = factorSvd.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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, 1)] + [Row(4, 4)] + [Row(7, 8)] + [Row(10, 10)] + [Row(45, 50)] + [Row(80, 100)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int row, int count) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, count); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(row, count); + var matrixX = factorSvd.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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, 1)] + [Row(2, 2)] + [Row(5, 5)] + [Row(9, 10)] + [Row(50, 50)] + [Row(90, 100)] + [MultipleAsserts] + public void CanSolveForRandomVectorWhenResultVectorGiven(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(row); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(column); + factorSvd.Solve(vectorb,resultx); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 1.0e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1.0e-9); + } + + // 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, 1)] + [Row(4, 4)] + [Row(7, 8)] + [Row(10, 10)] + [Row(45, 50)] + [Row(80, 100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixWhenResultMatrixGiven(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(column, column); + factorSvd.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].Real, matrixBReconstruct[i, j].Real, 1.0e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-9); + } + } + + // 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/Complex/MatrixLoader.cs b/src/UnitTests/LinearAlgebraTests/Complex/MatrixLoader.cs new file mode 100644 index 00000000..2433497b --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/MatrixLoader.cs @@ -0,0 +1,184 @@ +// +// 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.Complex +{ + using System.Collections.Generic; + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Generic; + using MbUnit.Framework; + + public abstract class MatrixLoader + { + protected Dictionary TestData2D; + protected Dictionary> TestMatrices; + + protected abstract Matrix CreateMatrix(int rows, int columns); + protected abstract Matrix CreateMatrix(Complex[,] data); + protected abstract Vector CreateVector(int size); + protected abstract Vector CreateVector(Complex[] data); + + [SetUp] + public virtual void SetupMatrices() + { + TestData2D = new Dictionary + { + { "Singular3x3", new[,] { { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1) }, { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1) }, { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1) } } }, + { "Square3x3", new[,] { { new Complex(-1.1, 1), new Complex(-2.2, 1), new Complex(-3.3, 1) }, { Complex.Zero, new Complex(1.1, 1), new Complex(2.2, 1) }, { new Complex(-4.4, 1), new Complex(5.5, 1), new Complex(6.6, 1) } } }, + { "Square4x4", new[,] { { new Complex(-1.1, 1), new Complex(-2.2, 1), new Complex(-3.3, 1), new Complex(-4.4, 1) }, { Complex.Zero, new Complex(1.1, 1), new Complex(2.2, 1), new Complex(3.3, 1) }, { new Complex(1.0, 1), new Complex(2.1, 1), new Complex(6.2, 1), new Complex(4.3, 1) }, { new Complex(-4.4, 1), new Complex(5.5, 1), new Complex(6.6, 1), new Complex(-7.7, 1) } } }, + { "Singular4x4", new[,] { { new Complex(-1.1, 1), new Complex(-2.2, 1), new Complex(-3.3, 1), new Complex(-4.4, 1) }, { new Complex(-1.1, 1), new Complex(-2.2, 1), new Complex(-3.3, 1), new Complex(-4.4, 1) }, { new Complex(-1.1, 1), new Complex(-2.2, 1), new Complex(-3.3, 1), new Complex(-4.4, 1) }, { new Complex(-1.1, 1), new Complex(-2.2, 1), new Complex(-3.3, 1), new Complex(-4.4, 1) } } }, + { "Tall3x2", new[,] { { new Complex(-1.1, 1), new Complex(-2.2, 1) }, { Complex.Zero, new Complex(1.1, 1) }, { new Complex(-4.4, 1), new Complex(5.5, 1) } } }, + { "Wide2x3", new[,] { { new Complex(-1.1, 1), new Complex(-2.2, 1), new Complex(-3.3, 1) }, { Complex.Zero, new Complex(1.1, 1), new Complex(2.2, 1) } } }, + }; + + TestMatrices = new Dictionary>(); + foreach (var name in TestData2D.Keys) + { + TestMatrices.Add(name, CreateMatrix(TestData2D[name])); + } + } + + public static Matrix GenerateRandomDenseMatrix(int row, int col) + { + // Fill a matrix with standard random numbers. + var normal = new Distributions.Normal + { + RandomSource = new Random.MersenneTwister(1) + }; + var matrixA = new DenseMatrix(row, col); + for (var i = 0; i < row; i++) + { + for (var j = 0; j < col; j++) + { + matrixA[i, j] = new Complex(normal.Sample(), normal.Sample()); + } + } + + // Generate a matrix which is positive definite. + return matrixA; + } + + public static Matrix GenerateRandomPositiveDefiniteHermitianDenseMatrix(int order) + { + // Fill a matrix with standard random numbers. + var normal = new Distributions.Normal + { + RandomSource = new Random.MersenneTwister(1) + }; + var matrixA = new DenseMatrix(order); + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + matrixA[i, j] = new Complex(normal.Sample(), normal.Sample()); + } + } + + // Generate a Hermitian matrix which is positive definite. + return matrixA.ConjugateTranspose() * matrixA; + } + + public static Vector GenerateRandomDenseVector(int order) + { + // Fill a matrix with standard random numbers. + var normal = new Distributions.Normal + { + RandomSource = new Random.MersenneTwister(1) + }; + var v = new DenseVector(order); + for (var i = 0; i < order; i++) + { + v[i] = new Complex(normal.Sample(), normal.Sample()); + } + + // Generate a matrix which is positive definite. + return v; + } + + public static Matrix GenerateRandomUserDefinedMatrix(int row, int col) + { + // Fill a matrix with standard random numbers. + var normal = new Distributions.Normal + { + RandomSource = new Random.MersenneTwister(1) + }; + var matrixA = new UserDefinedMatrix(row, col); + for (var i = 0; i < row; i++) + { + for (var j = 0; j < col; j++) + { + matrixA[i, j] = new Complex(normal.Sample(), normal.Sample()); + } + } + + // Generate a matrix which is positive definite. + return matrixA; + } + + public static Matrix GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(int order) + { + // Fill a matrix with standard random numbers. + var normal = new Distributions.Normal + { + RandomSource = new Random.MersenneTwister(1) + }; + var matrixA = new UserDefinedMatrix(order); + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + matrixA[i, j] = new Complex(normal.Sample(), normal.Sample()); + } + } + + // Generate a Hermitian matrix which is positive definite. + return matrixA.ConjugateTranspose() * matrixA; + } + + public static Vector GenerateRandomUserDefinedVector(int order) + { + // Fill a matrix with standard random numbers. + var normal = new Distributions.Normal + { + RandomSource = new Random.MersenneTwister(1) + }; + var v = new UserDefinedVector(order); + for (var i = 0; i < order; i++) + { + v[i] = new Complex(normal.Sample(), normal.Sample()); + } + + // Generate a matrix which is positive definite. + return v; + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/MatrixTests.Arithmetic.cs b/src/UnitTests/LinearAlgebraTests/Complex/MatrixTests.Arithmetic.cs new file mode 100644 index 00000000..920c015c --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/MatrixTests.Arithmetic.cs @@ -0,0 +1,894 @@ +// +// 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.Complex +{ + using System; + using System.Numerics; + using Distributions; + using LinearAlgebra.Complex; + using LinearAlgebra.Generic; + using MbUnit.Framework; + + public abstract partial class MatrixTests + { + [Test] + [Row(1.0, 1.0)] + [Row(2.0, 1.0)] + [Row(2.2, 1.0)] + [MultipleAsserts] + public void CanMultiplyWithComplex(double real, double imaginary) + { + var value = new Complex(real, imaginary); + var matrix = TestMatrices["Singular3x3"]; + var clone = matrix.Clone(); + clone.Multiply(value); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j] * value, clone[i, j]); + } + } + } + + [Test] + public void CanMultiplyWithVector() + { + var matrixA = TestMatrices["Singular3x3"]; + var x = new DenseVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1) }); + var y = matrixA * x; + + Assert.AreEqual(matrixA.RowCount, y.Count); + + for (var i = 0; i < matrixA.RowCount; i++) + { + var ar = matrixA.Row(i); + var dot = ar * x; + AssertHelpers.AreEqual(dot, y[i]); + } + } + + [Test] + public void CanMultiplyWithVectorIntoResult() + { + var matrixA = TestMatrices["Singular3x3"]; + var x = new DenseVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1) }); + var y = new DenseVector(3); + matrixA.Multiply(x, y); + + for (var i = 0; i < matrixA.RowCount; i++) + { + var ar = matrixA.Row(i); + var dot = ar * x; + AssertHelpers.AreEqual(dot, y[i]); + } + } + + [Test] + public void CanMultiplyWithVectorIntoResultWhenUpdatingInputArgument() + { + var matrixA = TestMatrices["Singular3x3"]; + var x = new DenseVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1) }); + var y = x; + matrixA.Multiply(x, x); + + Assert.AreSame(y, x); + + y = new DenseVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1) }); + for (var i = 0; i < matrixA.RowCount; i++) + { + var ar = matrixA.Row(i); + var dot = ar * y; + AssertHelpers.AreEqual(dot, x[i]); + } + } + + [Test] + [ExpectedArgumentNullException] + public void MultiplyWithVectorIntoResultFailsWhenResultIsNull() + { + var matrixA = TestMatrices["Singular3x3"]; + var x = new DenseVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1) }); + Vector y = null; + matrixA.Multiply(x, y); + } + + [Test] + [ExpectedArgumentException] + public void MultiplyWithVectorIntoResultFailsWhenResultIsTooLarge() + { + var matrixA = TestMatrices["Singular3x3"]; + var x = new DenseVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1) }); + Vector y = new DenseVector(4); + matrixA.Multiply(x, y); + } + + [Test] + [Row(1.0, 1.0)] + [Row(2.0, 1.0)] + [Row(2.2, 1.0)] + [MultipleAsserts] + public void CanOperatorLeftMultiplyWithComplex(double real, double imaginary) + { + var value = new Complex(real, imaginary); + var matrix = TestMatrices["Singular3x3"]; + var clone = matrix * value; + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j] * value, clone[i, j]); + } + } + } + + [Test] + [Row(1.0, 1.0)] + [Row(2.0, 1.0)] + [Row(2.2, 1.0)] + [MultipleAsserts] + public void CanOperatorRightMultiplyWithComplex(double real, double imaginary) + { + var value = new Complex(real, imaginary); + var matrix = TestMatrices["Singular3x3"]; + var clone = matrix * value; + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j] * value, clone[i, j]); + } + } + } + + [Test] + [Row(1.0, 1.0)] + [Row(2.0, 1.0)] + [Row(2.2, 1.0)] + [MultipleAsserts] + public void CanMultiplyWithComplexIntoResult(double real, double imaginary) + { + var value = new Complex(real, imaginary); + var matrix = TestMatrices["Singular3x3"]; + var result = matrix.Clone(); + matrix.Multiply(value, result); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j] * value, result[i, j]); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void MultiplyWithComplexIntoResultFailsWhenResultIsNull() + { + var matrix = TestMatrices["Singular3x3"]; + Matrix result = null; + matrix.Multiply(2.3, result); + } + + [Test] + [ExpectedArgumentException] + public void MultiplyWithComplexFailsWhenResultHasMoreRows() + { + var matrix = TestMatrices["Singular3x3"]; + var result = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + matrix.Multiply(2.3, result); + } + + [Test] + [ExpectedArgumentException] + public void MultiplyWithComplexFailsWhenResultHasMoreColumns() + { + var matrix = TestMatrices["Singular3x3"]; + var result = CreateMatrix(matrix.RowCount, matrix.ColumnCount + 1); + matrix.Multiply(2.3, result); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void OperatorLeftMultiplyWithComplexFailsWhenMatrixIsNull() + { + Matrix matrix = null; + var result = 2.3 * matrix; + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void OperatorRightMultiplyWithComplexFailsWhenMatrixIsNull() + { + Matrix matrix = null; + var result = matrix * 2.3; + } + + [Test] + [Row("Singular3x3", "Square3x3")] + [Row("Singular4x4", "Square4x4")] + public void CanAddMatrix(string mtxA, string mtxB) + { + var matrixA = TestMatrices[mtxA]; + var matrixB = TestMatrices[mtxB]; + + var matrix = matrixA.Clone(); + matrix.Add(matrixB); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], matrixA[i, j] + matrixB[i, j]); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddMatrixThrowsExceptionWhenArgumentIsNull() + { + var matrix = TestMatrices["Singular4x4"]; + Matrix other = null; + matrix.Add(other); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddMatrixThrowsExceptionArgumentHasTooFewColumns() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Tall3x2"]; + matrix.Add(other); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddMatrixThrowsExceptionArgumentHasTooFewRows() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Wide2x3"]; + matrix.Add(other); + } + + [Test] + [Row("Singular3x3", "Square3x3")] + [Row("Singular4x4", "Square4x4")] + public void AddOperator(string mtxA, string mtxB) + { + var matrixA = TestMatrices[mtxA]; + var matrixB = TestMatrices[mtxB]; + + var result = matrixA + matrixB; + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + AssertHelpers.AreEqual(result[i, j], matrixA[i, j] + matrixB[i, j]); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddOperatorThrowsExceptionWhenLeftsideIsNull() + { + Matrix matrix = null; + var other = TestMatrices["Singular3x3"]; + var result = matrix + other; + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void AddOperatorThrowsExceptionWhenRightsideIsNull() + { + var matrix = TestMatrices["Singular3x3"]; + Matrix other = null; + var result = matrix + other; + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddOperatorThrowsExceptionWhenRightsideHasTooFewColumns() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Tall3x2"]; + var result = matrix + other; + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void AddOperatorThrowsExceptionWhenRightsideHasTooFewRows() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Wide2x3"]; + var result = matrix + other; + } + + [Test] + [Row("Singular3x3", "Square3x3")] + [Row("Singular4x4", "Square4x4")] + public void CanSubtractMatrix(string mtxA, string mtxB) + { + var matrixA = TestMatrices[mtxA]; + var matrixB = TestMatrices[mtxB]; + + var matrix = matrixA.Clone(); + matrix.Subtract(matrixB); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], matrixA[i, j] - matrixB[i, j]); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SubtractMatrixThrowsExceptionWhenRightSideIsNull() + { + var matrix = TestMatrices["Singular4x4"]; + Matrix other = null; + matrix.Subtract(other); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SubtractMatrixThrowsExceptionWhenRightSideHasTooFewColumns() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Tall3x2"]; + matrix.Subtract(other); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SubtractMatrixThrowsExceptionWhenRightSideHasTooFewRows() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Wide2x3"]; + matrix.Subtract(other); + } + + [Test] + [Row("Singular3x3", "Square3x3")] + [Row("Singular4x4", "Square4x4")] + public void SubtractOperator(string mtxA, string mtxB) + { + var matrixA = TestMatrices[mtxA]; + var matrixB = TestMatrices[mtxB]; + + var result = matrixA - matrixB; + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + AssertHelpers.AreEqual(result[i, j], matrixA[i, j] - matrixB[i, j]); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SubtractOperatorThrowsExceptionWhenLeftsideIsNull() + { + Matrix matrix = null; + var other = TestMatrices["Singular3x3"]; + var result = matrix - other; + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void SubtractOperatorThrowsExceptionWhenRightsideIsNull() + { + var matrix = TestMatrices["Singular3x3"]; + Matrix other = null; + var result = matrix - other; + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SubtractOperatorThrowsExceptionWhenRightsideHasTooFewColumns() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Tall3x2"]; + var result = matrix - other; + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void SubtractOperatorThrowsExceptionWhenRightsideHasTooFewRows() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Wide2x3"]; + var result = matrix - other; + } + + [Test] + [Row("Singular3x3", "Square3x3")] + [Row("Singular4x4", "Square4x4")] + [Row("Wide2x3", "Square3x3")] + [Row("Wide2x3", "Tall3x2")] + [Row("Tall3x2", "Wide2x3")] + [MultipleAsserts] + public void CanMultiplyMatrixWithMatrix(string nameA, string nameB) + { + var matrixA = TestMatrices[nameA]; + var matrixB = TestMatrices[nameB]; + var matrixC = matrixA * matrixB; + + 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")] + [Row("Singular4x4")] + [Row("Wide2x3")] + [Row("Tall3x2")] + [MultipleAsserts] + public void CanTransposeAndMultiplyMatrixWithMatrix(string nameA) + { + var matrixA = TestMatrices[nameA]; + var matrixB = TestMatrices[nameA]; + var matrixC = matrixA.TransposeAndMultiply(matrixB); + + Assert.AreEqual(matrixC.RowCount, matrixA.RowCount); + Assert.AreEqual(matrixC.ColumnCount, matrixB.RowCount); + + for (var i = 0; i < matrixC.RowCount; i++) + { + for (var j = 0; j < matrixC.ColumnCount; j++) + { + AssertHelpers.AlmostEqual(matrixA.Row(i) * matrixB.Row(j), matrixC[i, j], 15); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void TransposeAndMultiplyMatrixMatrixFailsWhenSizesAreIncompatible() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Tall3x2"]; + var result = matrix.TransposeAndMultiply(other); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void TransposeAndMultiplyMatrixMatrixFailsWhenRightArgumentIsNull() + { + var matrix = 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 matrixA = TestMatrices[nameA]; + var matrixB = TestMatrices[nameA]; + var matrixC = CreateMatrix(matrixA.RowCount, matrixB.RowCount); + matrixA.TransposeAndMultiply(matrixB, matrixC); + + Assert.AreEqual(matrixC.RowCount, matrixA.RowCount); + Assert.AreEqual(matrixC.ColumnCount, matrixB.RowCount); + + for (var i = 0; i < matrixC.RowCount; i++) + { + for (var j = 0; j < matrixC.ColumnCount; j++) + { + AssertHelpers.AlmostEqual(matrixA.Row(i) * matrixB.Row(j), matrixC[i, j], 15); + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void MultiplyMatrixMatrixFailsWhenSizesAreIncompatible() + { + var matrix = TestMatrices["Singular3x3"]; + var other = TestMatrices["Wide2x3"]; + var result = matrix * other; + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void MultiplyMatrixMatrixFailsWhenLeftArgumentIsNull() + { + Matrix matrix = null; + var other = TestMatrices["Wide2x3"]; + var result = matrix * other; + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void MultiplyMatrixMatrixFailsWhenRightArgumentIsNull() + { + var matrix = TestMatrices["Wide2x3"]; + Matrix other = null; + var result = matrix * other; + } + + [Test] + [Row("Singular3x3", "Square3x3")] + [Row("Singular4x4", "Square4x4")] + [Row("Wide2x3", "Square3x3")] + [Row("Wide2x3", "Tall3x2")] + [Row("Tall3x2", "Wide2x3")] + [MultipleAsserts] + public virtual void CanMultiplyMatrixWithMatrixIntoResult(string nameA, string nameB) + { + var matrixA = TestMatrices[nameA]; + var matrixB = TestMatrices[nameB]; + var matrixC = CreateMatrix(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")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanNegate(string name) + { + var matrix = TestMatrices[name]; + var copy = matrix.Clone(); + + copy.Negate(); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(-matrix[i, j], copy[i, j]); + } + } + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanNegateIntoResult(string name) + { + var matrix = TestMatrices[name]; + var copy = matrix.Clone(); + + matrix.Negate(copy); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(-matrix[i, j], copy[i, j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void NegateIntoResultFailsWhenResultIsNull() + { + var matrix = TestMatrices["Singular3x3"]; + Matrix copy = null; + matrix.Negate(copy); + } + + [Test] + [ExpectedArgumentException] + public void NegateIntoResultFailsWhenResultHasMoreRows() + { + var matrix = TestMatrices["Singular3x3"]; + var target = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + matrix.Negate(target); + } + + [Test] + [ExpectedArgumentException] + public void NegateIntoResultFailsWhenResultHasMoreColumns() + { + var matrix = TestMatrices["Singular3x3"]; + var target = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + matrix.Negate(target); + } + + + [Test] + public void KroneckerProduct() + { + var matrixA = TestMatrices["Wide2x3"]; + var matrixB = TestMatrices["Square3x3"]; + var result = CreateMatrix(matrixA.RowCount * matrixB.RowCount, matrixA.ColumnCount * matrixB.ColumnCount); + matrixA.KroneckerProduct(matrixB, result); + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + for (var ii = 0; ii < matrixB.RowCount; ii++) + { + for (var jj = 0; jj < matrixB.ColumnCount; jj++) + { + AssertHelpers.AreEqual(result[i * matrixB.RowCount + ii, j * matrixB.ColumnCount + jj], matrixA[i, j] * matrixB[ii, jj]); + } + } + } + } + } + + [Test] + public void KroneckerProductResult() + { + var matrixA = TestMatrices["Wide2x3"]; + var matrixB = TestMatrices["Square3x3"]; + var result = matrixA.KroneckerProduct(matrixB); + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + for (var ii = 0; ii < matrixB.RowCount; ii++) + { + for (var jj = 0; jj < matrixB.ColumnCount; jj++) + { + AssertHelpers.AreEqual(result[i * matrixB.RowCount + ii, j * matrixB.ColumnCount + jj], matrixA[i, j] * matrixB[ii, jj]); + } + } + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(-4, ExpectedException = typeof(ArgumentOutOfRangeException))] + public void NormalizeColumns(int pValue) + { + var matrix = TestMatrices["Square4x4"]; + + var result = matrix.NormalizeColumns(pValue); + for (var j = 0; j < result.ColumnCount; j++) + { + var col = result.Column(j); + AssertHelpers.AlmostEqual(Complex.One, col.Norm(pValue), 12); + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(-3, ExpectedException = typeof(ArgumentOutOfRangeException))] + public void NormalizeRows(int pValue) + { + var matrix = TestMatrices["Square4x4"].NormalizeRows(pValue); + for (var i = 0; i < matrix.RowCount; i++) + { + var row = matrix.Row(i); + AssertHelpers.AlmostEqual(Complex.One, row.Norm(pValue), 12); + } + } + + [Test] + public void PointwiseMultiplyResult() + { + foreach (var data in TestMatrices.Values) + { + var other = data.Clone(); + var result = data.Clone(); + data.PointwiseMultiply(other, result); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(data[i, j] * other[i, j], result[i, j]); + } + } + + result = data.PointwiseMultiply(other); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(data[i, j] * other[i, j], result[i, j]); + } + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void PointwiseMultiplyWithNullOtherShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + Matrix other = null; + var result = matrix.Clone(); + matrix.PointwiseMultiply(other, result); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void PointwiseMultiplyWithResultNullShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + var other = matrix.Clone(); + matrix.PointwiseMultiply(other, null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void PointwiseMultiplyWithInvalidOtherMatrixDimensionsShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + var other = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + var result = matrix.Clone(); + matrix.PointwiseMultiply(other, result); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void PointwiseMultiplyWithInvalidResultMatrixDimensionsShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + var other = matrix.Clone(); + var result = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + matrix.PointwiseMultiply(other, result); + } + + [Test] + public virtual void PointwiseDivideResult() + { + foreach (var data in TestMatrices.Values) + { + var other = data.Clone(); + var result = data.Clone(); + data.PointwiseDivide(other, result); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(data[i, j] / other[i, j], result[i, j]); + } + } + + result = data.PointwiseDivide(other); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(data[i, j] / other[i, j], result[i, j]); + } + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void PointwiseDivideWithNullOtherShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + Matrix other = null; + var result = matrix.Clone(); + matrix.PointwiseDivide(other, result); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void PointwiseDivideWithResultNullShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + var other = matrix.Clone(); + matrix.PointwiseDivide(other, null); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void PointwiseDivideWithInvalidOtherMatrixDimensionsShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + var other = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + var result = matrix.Clone(); + matrix.PointwiseDivide(other, result); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void PointwiseDivideWithInvalidResultMatrixDimensionsShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + var other = matrix.Clone(); + var result = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + matrix.PointwiseDivide(other, result); + } + + [Test] + [Row(0, ExpectedException = typeof(ArgumentException))] + [Row(-2, ExpectedException = typeof(ArgumentException))] + public void RandomWithNonPositiveNumberOfRowsShouldThrowException(int numberOfRows) + { + var matrix = CreateMatrix(2, 3); + matrix = matrix.Random(numberOfRows, 4, new ContinuousUniform()); + } + + [Test] + [Row(0, ExpectedException = typeof(ArgumentException))] + [Row(-2, ExpectedException = typeof(ArgumentException))] + public void RandomWithNonPositiveNumberOfRowsShouldThrowException2(int numberOfRows) + { + var matrix = CreateMatrix(2, 3); + matrix = matrix.Random(numberOfRows, 4, new DiscreteUniform(0, 2)); + } + + [Test] + public void Trace() + { + var matrix = TestMatrices["Square3x3"]; + var trace = matrix.Trace(); + AssertHelpers.AreEqual(new Complex(6.6, 3), trace); + } + + [Test] + [ExpectedArgumentException] + public void TraceOfNonSquareMatrixShouldThrowException() + { + var matrix = TestMatrices["Wide2x3"]; + var trace = matrix.Trace(); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/MatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/MatrixTests.cs new file mode 100644 index 00000000..5f3c8731 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/MatrixTests.cs @@ -0,0 +1,1499 @@ +// +// 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.Complex +{ + using System; + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Generic; + using MbUnit.Framework; + + [TestFixture] + public abstract partial class MatrixTests : MatrixLoader + { + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanCloneMatrix(string name) + { + var matrix = CreateMatrix(TestData2D[name]); + var clone = matrix.Clone(); + + Assert.AreNotSame(matrix, clone); + Assert.AreEqual(matrix.RowCount, clone.RowCount); + Assert.AreEqual(matrix.ColumnCount, clone.ColumnCount); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], clone[i, j]); + } + } + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanCloneMatrixUsingICloneable(string name) + { + var matrix = TestMatrices[name]; + var clone = (Matrix)((ICloneable)matrix).Clone(); + + Assert.AreNotSame(matrix, clone); + Assert.AreEqual(matrix.RowCount, clone.RowCount); + Assert.AreEqual(matrix.ColumnCount, clone.ColumnCount); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], clone[i, j]); + } + } + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanCopyTo(string name) + { + var matrix = TestMatrices[name]; + var copy = CreateMatrix(matrix.RowCount, matrix.ColumnCount); + matrix.CopyTo(copy); + + Assert.AreNotSame(matrix, copy); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], copy[i, j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void CopyToFailsWhenTargetIsNull() + { + var matrix = TestMatrices["Singular3x3"]; + Matrix target = null; + matrix.CopyTo(target); + } + + [Test] + [ExpectedArgumentException] + public void CopyToFailsWhenTargetHasMoreRows() + { + var matrix = TestMatrices["Singular3x3"]; + var target = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + matrix.CopyTo(target); + } + + [Test] + [ExpectedArgumentException] + public void CopyToFailsWhenTargetHasMoreColumns() + { + var matrix = TestMatrices["Singular3x3"]; + var target = CreateMatrix(matrix.RowCount + 1, matrix.ColumnCount); + matrix.CopyTo(target); + } + + [Test] + [Ignore] + public void CanConvertVectorToString() + { + } + + [Test] + public void CanCreateMatrix() + { + var expected = CreateMatrix(5, 6); + var actual = expected.CreateMatrix(5, 6); + Assert.AreEqual(expected.GetType(), actual.GetType(), "Matrices are same type."); + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanEquateMatrices(string name) + { + var matrix1 = CreateMatrix(TestData2D[name]); + var matrix2 = CreateMatrix(TestData2D[name]); + var matrix3 = CreateMatrix(TestData2D[name].GetLength(0), TestData2D[name].GetLength(1)); + Assert.IsTrue(matrix1.Equals(matrix1)); + Assert.IsTrue(matrix1.Equals(matrix2)); + Assert.IsFalse(matrix1.Equals(matrix3)); + Assert.IsFalse(matrix1.Equals(null)); + } + + [Test] + [Row(0, 2)] + [Row(2, 0)] + [Row(0, 0)] + [Row(-1, 1)] + [Row(1, -1)] + [ExpectedArgumentOutOfRangeException] + public void ThrowsArgumentExceptionIfSizeIsNotPositive(int rows, int columns) + { + CreateMatrix(rows, columns); + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + public void TestingForEqualityWithNonMatrixReturnsFalse(string name) + { + var matrix = CreateMatrix(TestData2D[name]); + Assert.IsFalse(matrix.Equals(2)); + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + public void CanTestForEqualityUsingObjectEquals(string name) + { + var matrix1 = CreateMatrix(TestData2D[name]); + var matrix2 = CreateMatrix(TestData2D[name]); + Assert.IsTrue(matrix1.Equals((object)matrix2)); + } + + [Test] + [Row(-1, 1, "Singular3x3")] + [Row(1, -1, "Singular3x3")] + [Row(4, 2, "Square3x3")] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void RangeCheckFails(int i, int j, string name) + { + var x = TestMatrices[name][i, j]; + } + + [Test] + [Ignore] + public void MatrixGetHashCode() + { + } + + [Test] + public void CanClearMatrix() + { + var matrix = TestMatrices["Singular3x3"].Clone(); + matrix.Clear(); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(0, matrix[i, j]); + } + } + } + + [Test] + [Row(0, "Singular3x3")] + [Row(1, "Singular3x3")] + [Row(2, "Singular3x3")] + [Row(2, "Square3x3")] + public void CanGetRow(int rowIndex, string name) + { + var matrix = TestMatrices[name]; + var row = matrix.Row(rowIndex); + + Assert.AreEqual(matrix.ColumnCount, row.Count); + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[rowIndex, j], row[j]); + } + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetRowThrowsArgumentOutOfRangeWithNegativeIndex() + { + var matrix = TestMatrices["Singular3x3"]; + matrix.Row(-1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetRowThrowsArgumentOutOfRangeWithOverflowingRowIndex() + { + var matrix = TestMatrices["Singular3x3"]; + matrix.Row(matrix.RowCount); + } + + [Test] + [Row(0, "Singular3x3")] + [Row(1, "Singular3x3")] + [Row(2, "Singular3x3")] + [Row(2, "Square3x3")] + public void CanGetRowWithResult(int rowIndex, string name) + { + var matrix = TestMatrices[name]; + var row = CreateVector(matrix.ColumnCount); + matrix.Row(rowIndex, row); + + Assert.AreEqual(matrix.ColumnCount, row.Count); + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[rowIndex, j], row[j]); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetRowWithResultFailsWhenResultIsNull() + { + var matrix = TestMatrices["Singular3x3"]; + matrix.Row(0, null); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetRowWithResultThrowsArgumentOutOfRangeWithNegativeIndex() + { + var matrix = TestMatrices["Singular3x3"]; + var row = CreateVector(matrix.ColumnCount); + matrix.Row(-1, row); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetRowWithResultThrowsArgumentOutOfRangeWithOverflowingRowIndex() + { + var matrix = TestMatrices["Singular3x3"]; + var row = CreateVector(matrix.ColumnCount); + matrix.Row(matrix.RowCount, row); + } + + [Test] + [Row(0, 0, 1, "Singular3x3")] + [Row(1, 1, 2, "Singular3x3")] + [Row(2, 0, 3, "Singular3x3")] + [Row(2, 0, 3, "Square3x3")] + public void CanGetRowWithRange(int rowIndex, int start, int length, string name) + { + var matrix = TestMatrices[name]; + var row = matrix.Row(rowIndex, start, length); + + Assert.AreEqual(length, row.Count); + for (var j = start; j < start + length; j++) + { + AssertHelpers.AreEqual(matrix[rowIndex, j], row[j - start]); + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void GetRowWithRangeResultArgumentExeptionWhenLengthIsZero() + { + var matrix = TestMatrices["Singular3x3"]; + var result = CreateVector(matrix.ColumnCount); + matrix.Row(0, 0, 0, result); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void GetRowWithRangeFailsWithTooSmallResultVector() + { + var matrix = TestMatrices["Singular3x3"]; + var result = CreateVector(matrix.ColumnCount - 1); + matrix.Row(0, 0, 0, result); + } + + [Test] + [Row(0, "Singular3x3")] + [Row(1, "Singular3x3")] + [Row(2, "Singular3x3")] + [Row(2, "Square3x3")] + public void CanGetColumn(int colIndex, string name) + { + var matrix = TestMatrices[name]; + var col = matrix.Column(colIndex); + + Assert.AreEqual(matrix.RowCount, col.Count); + for (var j = 0; j < matrix.RowCount; j++) + { + AssertHelpers.AreEqual(matrix[j, colIndex], col[j]); + } + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetColumnThrowsArgumentOutOfRangeWithNegativeIndex() + { + var matrix = TestMatrices["Singular3x3"]; + matrix.Column(-1); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetColumnThrowsArgumentOutOfRangeWithOverflowingRowIndex() + { + var matrix = TestMatrices["Singular3x3"]; + matrix.Column(matrix.ColumnCount); + } + + [Test] + [Row(0, "Singular3x3")] + [Row(1, "Singular3x3")] + [Row(2, "Singular3x3")] + [Row(2, "Square3x3")] + public void CanGetColumnWithResult(int colIndex, string name) + { + var matrix = TestMatrices[name]; + var col = CreateVector(matrix.RowCount); + matrix.Column(colIndex, col); + + Assert.AreEqual(matrix.RowCount, col.Count); + for (var j = 0; j < matrix.RowCount; j++) + { + AssertHelpers.AreEqual(matrix[j, colIndex], col[j]); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void GetColumnFailsWhenResultIsNull() + { + var matrix = TestMatrices["Singular3x3"]; + matrix.Column(0, null); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetColumnWithResultThrowsArgumentOutOfRangeWithNegativeIndex() + { + var matrix = TestMatrices["Singular3x3"]; + var column = CreateVector(matrix.ColumnCount); + matrix.Column(-1, column); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + public void GetColumnWithResultThrowsArgumentOutOfRangeWithOverflowingRowIndex() + { + var matrix = TestMatrices["Singular3x3"]; + var column = CreateVector(matrix.RowCount); + matrix.Row(matrix.ColumnCount, column); + } + + [Test] + [Row(0, 0, 1, "Singular3x3")] + [Row(1, 1, 2, "Singular3x3")] + [Row(2, 0, 3, "Singular3x3")] + [Row(2, 0, 3, "Square3x3")] + public void CanGetColumnWithRange(int colIndex, int start, int length, string name) + { + var matrix = TestMatrices[name]; + var col = matrix.Column(colIndex, start, length); + + Assert.AreEqual(length, col.Count); + for (var j = start; j < start + length; j++) + { + AssertHelpers.AreEqual(matrix[j, colIndex], col[j - start]); + } + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void GetColumnWithRangeResultArgumentExeptionWhenLengthIsZero() + { + var matrix = TestMatrices["Singular3x3"]; + var col = CreateVector(matrix.RowCount); + matrix.Column(0, 0, 0, col); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void GetColumnWithRangeFailsWithTooSmallResultVector() + { + var matrix = TestMatrices["Singular3x3"]; + var result = CreateVector(matrix.RowCount - 1); + matrix.Column(0, 0, matrix.RowCount, result); + } + + [Test] + [Row(0, "Singular3x3")] + [Row(1, "Singular3x3")] + [Row(2, "Singular3x3")] + [Row(2, "Square3x3")] + public void CanSetRow(int rowIndex, string name) + { + var matrix = TestMatrices[name].Clone(); + matrix.SetRow(rowIndex, CreateVector(matrix.ColumnCount)); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(i == rowIndex ? 0.0 : TestMatrices[name][i, j], matrix[i, j]); + } + } + } + + [Test] + [Row(0, "Singular3x3")] + [Row(1, "Singular3x3")] + [Row(2, "Singular3x3")] + [Row(2, "Square3x3")] + public void CanSetColumn(int colIndex, string name) + { + var matrix = TestMatrices[name].Clone(); + matrix.SetColumn(colIndex, CreateVector(matrix.ColumnCount)); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(j == colIndex ? 0.0 : TestMatrices[name][i, j], matrix[i, j]); + } + } + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void UpperTriangle(string name) + { + var data = TestMatrices[name]; + var upper = data.UpperTriangle(); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(i <= j ? data[i, j] : 0, upper[i, j]); + } + } + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void UpperTriangleResult(string name) + { + var data = TestMatrices[name]; + var upper = CreateMatrix(data.RowCount, data.ColumnCount); + data.UpperTriangle(upper); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(i <= j ? data[i, j] : 0, upper[i, j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void UpperTriangleWithResultNullShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + Matrix result = null; + data.UpperTriangle(result); + } + + [Test] + [ExpectedArgumentException] + public void UpperTriangleWithUnEqualRowsShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + var result = CreateMatrix(data.RowCount + 1, data.ColumnCount); + data.UpperTriangle(result); + } + + [Test] + [ExpectedArgumentException] + public void UpperTriangleWithUnEqualColumnsShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + var result = CreateMatrix(data.RowCount, data.ColumnCount + 1); + data.UpperTriangle(result); + } + + [Test] + public void StrictlyLowerTriangle() + { + foreach (var data in TestMatrices.Values) + { + var lower = data.StrictlyLowerTriangle(); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(i > j ? data[i, j] : 0, lower[i, j]); + } + } + } + } + + [Test] + public void StrictlyLowerTriangleResult() + { + foreach (var data in TestMatrices.Values) + { + var lower = CreateMatrix(data.RowCount, data.ColumnCount); + data.StrictlyLowerTriangle(lower); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(i > j ? data[i, j] : 0, lower[i, j]); + } + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void StrictlyLowerTriangleWithNullParameterShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + Matrix lower = null; + data.StrictlyLowerTriangle(lower); + } + + [Test] + [ExpectedArgumentException] + public void StrictlyLowerTriangleWithInvalidColumnNumberShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + var lower = CreateMatrix(data.RowCount, data.ColumnCount + 1); + data.StrictlyLowerTriangle(lower); + } + + [Test] + [ExpectedArgumentException] + public void StrictlyLowerTriangleWithInvalidRowNumberShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + var lower = CreateMatrix(data.RowCount + 1, data.ColumnCount); + data.StrictlyLowerTriangle(lower); + } + + + [Test] + public void StrictlyUpperTriangle() + { + foreach (var data in TestMatrices.Values) + { + var upper = data.StrictlyUpperTriangle(); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(i < j ? data[i, j] : 0, upper[i, j]); + } + } + } + } + + [Test] + public void StrictlyUpperTriangleResult() + { + foreach (var data in TestMatrices.Values) + { + var upper = CreateMatrix(data.RowCount, data.ColumnCount); + data.StrictlyUpperTriangle(upper); + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(i < j ? data[i, j] : 0, upper[i, j]); + } + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void StrictlyUpperTriangleWithNullParameterShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + Matrix lower = null; + data.StrictlyUpperTriangle(lower); + } + + [Test] + [ExpectedArgumentException] + public void StrictlyUpperTriangleWithInvalidColumnNumberShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + var lower = CreateMatrix(data.RowCount, data.ColumnCount + 1); + data.StrictlyUpperTriangle(lower); + } + + [Test] + [ExpectedArgumentException] + public void StrictlyUpperTriangleWithInvalidRowNumberShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + var lower = CreateMatrix(data.RowCount + 1, data.ColumnCount); + data.StrictlyUpperTriangle(lower); + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanTransposeMatrix(string name) + { + var matrix = CreateMatrix(TestData2D[name]); + var transpose = matrix.Transpose(); + + Assert.AreNotSame(matrix, transpose); + Assert.AreEqual(matrix.RowCount, transpose.ColumnCount); + Assert.AreEqual(matrix.ColumnCount, transpose.RowCount); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], transpose[j, i]); + } + } + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + [MultipleAsserts] + public void CanConjugateTransposeMatrix(string name) + { + var matrix = CreateMatrix(TestData2D[name]); + var transpose = matrix.ConjugateTranspose(); + + Assert.AreNotSame(matrix, transpose); + Assert.AreEqual(matrix.RowCount, transpose.ColumnCount); + Assert.AreEqual(matrix.ColumnCount, transpose.RowCount); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], transpose[j, i].Conjugate()); + } + } + } + + [Test] + [Row("Singular3x3", new double[] { 1, 2, 3 })] + [Row("Square3x3", new double[] { 1, 2, 3 })] + [Row("Tall3x2", new double[] { 1, 2, 3 })] + [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 virtual void SetColumnWithArray(string name, double[] real) + { + Complex[] column = null; + if (real != null) + { + column = new Complex[real.Length]; + for (int i = 0; i < real.Length; i++) + { + column[i] = new Complex(real[i], 1); + } + } + + var matrix = TestMatrices[name]; + for (var i = 0; i < matrix.ColumnCount; i++) + { + matrix.SetColumn(i, column); + for (var j = 0; j < matrix.RowCount; j++) + { + AssertHelpers.AreEqual(matrix[j, i], column[j]); + } + } + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetColumnArrayWithInvalidColumnIndexShouldThrowException() + { + var matrix = TestMatrices["Square3x3"]; + Complex[] column = { 1, 2, 3 }; + matrix.SetColumn(-1, column); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetColumnArrayWithInvalidColumnIndexShouldThrowException2() + { + var matrix = TestMatrices["Square3x3"]; + Complex[] column = { 1, 2, 3 }; + matrix.SetColumn(matrix.ColumnCount + 1, column); + } + + [Test] + [Row("Singular3x3", new double[] { 1, 2, 3 })] + [Row("Square3x3", new double[] { 1, 2, 3 })] + [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 virtual void SetColumnWithVector(string name, double[] real) + { + var column = new Complex[real.Length]; + for (int i = 0; i < real.Length; i++) + { + column[i] = new Complex(real[i], 1); + } + + var matrix = TestMatrices[name]; + var columnVector = CreateVector(column); + for (var i = 0; i < matrix.ColumnCount; i++) + { + matrix.SetColumn(i, column); + for (var j = 0; j < matrix.RowCount; j++) + { + AssertHelpers.AreEqual(matrix[j, i], columnVector[j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void SetColumnWithNullVectorShouldThrowException() + { + var matrix = TestMatrices["Square3x3"]; + Vector columnVector = null; + matrix.SetColumn(1, columnVector); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetColumnVectorWithInvalidColumnIndexShouldThrowException() + { + var matrix = TestMatrices["Square3x3"]; + var column = CreateVector(new Complex[] { 1, 2, 3 }); + matrix.SetColumn(-1, column); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetColumnVectorWithInvalidColumnIndexShouldThrowException2() + { + var matrix = TestMatrices["Square3x3"]; + var column = CreateVector(new Complex[] { 1, 2, 3 }); + matrix.SetColumn(matrix.ColumnCount + 1, column); + } + + [Test] + public void InsertColumn() + { + var matrix = CreateMatrix(3, 3); + var column = CreateVector(matrix.RowCount); + for (var i = 0; i < column.Count; i++) + { + column[i] = i; + } + + for (var k = 0; k < matrix.ColumnCount + 1; k++) + { + var result = matrix.InsertColumn(k, column); + Assert.AreEqual(result.ColumnCount, matrix.ColumnCount + 1); + for (var col = 0; col < result.ColumnCount; col++) + { + for (var row = 0; row < result.RowCount; row++) + { + AssertHelpers.AreEqual(col == k ? row : 0, result[row, col]); + } + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void InsertNullColumnShouldThrowExecption() + { + var matrix = TestMatrices["Square3x3"]; + matrix.InsertColumn(0, null); + } + + [Test] + [Row(-1, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(5, ExpectedException = typeof(ArgumentOutOfRangeException))] + public void InsertColumnWithInvalidColumnIndexShouldThrowExceptiopn(int columnIndex) + { + var matrix = CreateMatrix(3, 3); + var column = CreateVector(matrix.RowCount); + matrix.InsertColumn(columnIndex, column); + } + + public void InsertColumnWithInvalidNumberOfElementsShouldThrowException() + { + var matrix = CreateMatrix(3, 3); + var column = CreateVector(matrix.RowCount + 1); + matrix.InsertColumn(0, column); + } + + [Test] + [Row("Singular3x3", new double[] { 1, 2, 3 })] + [Row("Square3x3", new double[] { 1, 2, 3 })] + [Row("Tall3x2", new double[] { 1, 2 })] + [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 virtual void SetRowWithArray(string name, double[] real) + { + Complex[] row = null; + if (real != null) + { + row = new Complex[real.Length]; + for (var i = 0; i < real.Length; i++) + { + row[i] = new Complex(real[i], 1); + } + } + + var matrix = TestMatrices[name]; + for (var i = 0; i < matrix.RowCount; i++) + { + matrix.SetRow(i, row); + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], row[j]); + } + } + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetRowArrayWithInvalidRowIndexShouldThrowException() + { + var matrix = TestMatrices["Square3x3"]; + Complex[] row = { 1, 2, 3 }; + matrix.SetRow(-1, row); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetRowArrayWithInvalidRowIndexShouldThrowException2() + { + var matrix = TestMatrices["Square3x3"]; + Complex[] row = { 1, 2, 3 }; + matrix.SetRow(matrix.RowCount + 1, row); + } + + [Test] + [Row("Singular3x3", new double[] { 1, 2, 3 })] + [Row("Square3x3", new double[] { 1, 2, 3 })] + [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 virtual void SetRowWithVector(string name, double[] real) + { + var row = new Complex[real.Length]; + for (var i = 0; i < real.Length; i++) + { + row[i] = new Complex(real[i], 1); + } + + var matrix = TestMatrices[name]; + var rowVector = CreateVector(row); + for (var i = 0; i < matrix.RowCount; i++) + { + matrix.SetRow(i, row); + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], rowVector[j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void SetRowWithNullVectorShouldThrowException() + { + var matrix = TestMatrices["Square3x3"]; + Vector rowVector = null; + matrix.SetRow(1, rowVector); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetRowVectorWithInvalidRowIndexShouldThrowException() + { + var matrix = TestMatrices["Square3x3"]; + var row = CreateVector(new Complex[] { 1, 2, 3 }); + matrix.SetRow(-1, row); + } + + [Test] + [ExpectedArgumentOutOfRangeException] + public void SetRowVectorWithInvalidRowIndexShouldThrowException2() + { + var matrix = TestMatrices["Square3x3"]; + var row = CreateVector(new Complex[] { 1, 2, 3 }); + matrix.SetRow(matrix.RowCount + 1, row); + } + + [Test] + [Row(0, 2, 0, 2)] + [Row(1, 1, 1, 1)] + [Row(0, 4, 0, 2, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(0, 2, 0, 4, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(4, 2, 0, 2, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(0, 2, 4, 2, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(-1, 2, 0, 2, ExpectedException = typeof(ArgumentOutOfRangeException))] + [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 virtual void SetSubMatrix(int rowStart, int rowLength, int colStart, int colLength) + { + foreach (var matrix in TestMatrices.Values) + { + var subMatrix = matrix.SubMatrix(0, 2, 0, 2); + subMatrix[0, 0] = 10.0; + subMatrix[0, 1] = -1.0; + subMatrix[1, 0] = 3.0; + subMatrix[1, 1] = 4.0; + matrix.SetSubMatrix(rowStart, rowLength, colStart, colLength, subMatrix); + + for (int i = rowStart, ii = 0; i < rowLength; i++, ii++) + { + for (int j = colStart, jj = 0; j < colLength; j++, jj++) + { + AssertHelpers.AreEqual(matrix[i, j], subMatrix[ii, jj]); + } + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void SetSubMatrixWithNullSubMatrixShouldThrowException() + { + var data = TestMatrices["Square3x3"]; + Matrix subMatrix = null; + data.SetSubMatrix(0, 2, 0, 2, subMatrix); + } + + [Test] + [Row("Square3x3", new double[] { 1, 2, 3 })] + [Row("Wide2x3", new double[] { 1, 2 })] + [Row("Wide2x3", new double[] { 1, 2, 3 }, ExpectedException = typeof(ArgumentException))] + [Row("Tall3x2", new double[] { 1, 2 })] + public void SetDiagonalVector(string name, double[] real) + { + var diagonal = new Complex[real.Length]; + for (var i = 0; i < real.Length; i++) + { + diagonal[i] = new Complex(real[i], 1); + } + + + var matrix = TestMatrices[name]; + var vector = CreateVector(diagonal); + matrix.SetDiagonal(vector); + + var min = Math.Min(matrix.ColumnCount, matrix.RowCount); + Assert.AreEqual(diagonal.Length, min); + + for (var i = 0; i < vector.Count; i++) + { + AssertHelpers.AreEqual(vector[i], matrix[i, i]); + } + } + + [Test] + [ExpectedArgumentNullException] + public void SetDiagonalWithNullVectorParameterShouldThrowException() + { + var matrix = TestMatrices["Square3x3"]; + Vector vector = null; + matrix.SetDiagonal(vector); + } + + [Test] + [Row("Square3x3", new double[] { 1, 2, 3 })] + [Row("Wide2x3", new double[] { 1, 2 })] + [Row("Wide2x3", new double[] { 1, 2, 3 }, ExpectedException = typeof(ArgumentException))] + [Row("Tall3x2", new double[] { 1, 2 })] + [Row("Square3x3", null, ExpectedException = typeof(ArgumentNullException))] + public void SetDiagonalArray(string name, double[] real) + { + Complex[] diagonal = null; + if (real != null) + { + diagonal = new Complex[real.Length]; + for (var i = 0; i < real.Length; i++) + { + diagonal[i] = new Complex(real[i], 1); + } + } + + var matrix = TestMatrices[name]; + matrix.SetDiagonal(diagonal); + var min = Math.Min(matrix.ColumnCount, matrix.RowCount); + Assert.AreEqual(diagonal.Length, min); + for (var i = 0; i < diagonal.Length; i++) + { + AssertHelpers.AreEqual(diagonal[i], matrix[i, i]); + } + } + + [Test] + public void InsertRow() + { + var matrix = CreateMatrix(3, 3); + var row = CreateVector(matrix.ColumnCount); + for (var i = 0; i < row.Count; i++) + { + row[i] = i; + } + + for (var insertedRowIndex = 0; insertedRowIndex < matrix.RowCount + 1; insertedRowIndex++) + { + var result = matrix.InsertRow(insertedRowIndex, row); + Assert.AreEqual(result.RowCount, matrix.ColumnCount + 1); + for (var i = 0; i < result.RowCount; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + AssertHelpers.AreEqual(i == insertedRowIndex ? row[j] : 0, result[i, j]); + } + } + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void InsertNullRowShouldThrowExecption() + { + var matrix = TestMatrices["Square3x3"]; + matrix.InsertRow(0, null); + } + + [Test] + [Row(-1, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(5, ExpectedException = typeof(ArgumentOutOfRangeException))] + public void InsertRowWithInvalidRowIndexShouldThrowExceptiopn(int rowIndex) + { + var matrix = CreateMatrix(3, 3); + var row = CreateVector(matrix.ColumnCount); + matrix.InsertRow(rowIndex, row); + } + + public void InsertRowWithInvalidNumberOfElementsShouldThrowException() + { + var matrix = CreateMatrix(3, 3); + var row = CreateVector(matrix.ColumnCount + 1); + matrix.InsertRow(0, row); + } + + [Test] + public void ToArray() + { + foreach (var data in TestMatrices.Values) + { + var array = data.ToArray(); + Assert.AreEqual(data.RowCount, array.GetLength(0)); + Assert.AreEqual(data.ColumnCount, array.GetLength(1)); + + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(data[i, j], array[i, j]); + } + } + } + } + + [Test] + public void ToColumnWiseArray() + { + foreach (var data in TestMatrices.Values) + { + var array = data.ToColumnWiseArray(); + Assert.AreEqual(data.RowCount * data.ColumnCount, array.Length); + + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(data[i, j], array[j * data.RowCount + i]); + } + } + } + } + + [Test] + public void ToRowWiseArray() + { + foreach (var data in TestMatrices.Values) + { + var array = data.ToRowWiseArray(); + Assert.AreEqual(data.RowCount * data.ColumnCount, array.Length); + + for (var i = 0; i < data.RowCount; i++) + { + for (var j = 0; j < data.ColumnCount; j++) + { + AssertHelpers.AreEqual(data[i, j], array[i * data.ColumnCount + j]); + } + } + } + } + + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Tall3x2")] + [MultipleAsserts] + public virtual void CanPermuteMatrixRows(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++) + { + AssertHelpers.AreEqual(matrix[i, j], matrixp[permutation[i], j]); + } + } + } + + [Test] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Wide2x3")] + [MultipleAsserts] + public virtual void CanPermuteMatrixColumns(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++) + { + AssertHelpers.AreEqual(matrix[i, j], matrixp[i, permutation[j]]); + } + } + } + + [Test] + public void CanAppendMatrices() + { + var left = CreateMatrix(TestData2D["Singular3x3"]); + var right = CreateMatrix(TestData2D["Tall3x2"]); + var result = left.Append(right); + Assert.AreEqual(left.ColumnCount + right.ColumnCount, result.ColumnCount); + Assert.AreEqual(left.RowCount, right.RowCount); + + for (var i = 0; i < result.RowCount; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + AssertHelpers.AreEqual(j < left.ColumnCount ? left[i, j] : right[i, j - left.ColumnCount], result[i, j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void CanAppendWithRightParameterNullShouldThrowException() + { + var left = TestMatrices["Square3x3"]; + Matrix right = null; + left.Append(right); + } + + [Test] + [ExpectedArgumentNullException] + public void CanAppendWithResultParameterNullShouldThrowException() + { + var left = TestMatrices["Square3x3"]; + var right = TestMatrices["Tall3x2"]; + Matrix result = null; + left.Append(right, result); + } + + [Test] + [ExpectedArgumentException] + public void AppendingTwoMatricesWithDifferentRowCountShouldThrowException() + { + var left = TestMatrices["Square3x3"]; + var right = TestMatrices["Wide2x3"]; + var result = left.Append(right); + } + + [Test] + [ExpectedArgumentException] + public void AppendingWithInvalidResultMatrixColumnsShouldThrowException() + { + var left = TestMatrices["Square3x3"]; + var right = TestMatrices["Tall3x2"]; + var result = CreateMatrix(3, 2); + left.Append(right, result); + } + + [Test] + public void CanStackMatrices() + { + var top = TestMatrices["Square3x3"]; + var bottom = TestMatrices["Wide2x3"]; + var result = top.Stack(bottom); + Assert.AreEqual(top.RowCount + bottom.RowCount, result.RowCount); + Assert.AreEqual(top.ColumnCount, result.ColumnCount); + + for (var i = 0; i < result.RowCount; i++) + { + for (var j = 0; j < result.ColumnCount; j++) + { + AssertHelpers.AreEqual(result[i, j], i < top.RowCount ? top[i, j] : bottom[i - top.RowCount, j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void StackingWithBottomParameterNullShouldThrowException() + { + var top = TestMatrices["Square3x3"]; + Matrix bottom = null; + var result = CreateMatrix(top.RowCount + top.RowCount, top.ColumnCount); + top.Stack(bottom, result); + } + + [Test] + [ExpectedArgumentNullException] + public void StackingWithResultParameterNullShouldThrowException() + { + var top = TestMatrices["Square3x3"]; + var bottom = TestMatrices["Square3x3"]; + Matrix result = null; + top.Stack(bottom, result); + } + + [Test] + [ExpectedArgumentException] + public void StackingTwoMatricesWithDifferentColumnsShouldThrowException() + { + var top = TestMatrices["Square3x3"]; + var lower = TestMatrices["Tall3x2"]; + var result = CreateMatrix(top.RowCount + lower.RowCount, top.ColumnCount); + top.Stack(lower, result); + } + + [Test] + [ExpectedArgumentException] + public void StackingWithInvalidResultMatrixRowsShouldThrowException() + { + var top = TestMatrices["Square3x3"]; + var bottom = TestMatrices["Wide2x3"]; + var result = CreateMatrix(1, 3); + top.Stack(bottom, result); + } + + [Test] + public void CanDiagonallyStackMatrics() + { + var top = TestMatrices["Tall3x2"]; + var bottom = TestMatrices["Wide2x3"]; + var result = top.DiagonalStack(bottom); + 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) + { + AssertHelpers.AreEqual(top[i, j], result[i, j]); + } + else if (i >= top.RowCount && j >= top.ColumnCount) + { + AssertHelpers.AreEqual(bottom[i - top.RowCount, j - top.ColumnCount], result[i, j]); + } + else + { + AssertHelpers.AreEqual(0, result[i, j]); + } + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void DiagonalStackWithLowerNullShouldThrowException() + { + var top = TestMatrices["Square3x3"]; + Matrix lower = null; + top.DiagonalStack(lower); + } + + [Test] + public virtual void CanDiagonallyStackMatricesWithPassingResult() + { + var top = TestMatrices["Tall3x2"]; + var bottom = TestMatrices["Wide2x3"]; + var result = CreateMatrix(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) + { + AssertHelpers.AreEqual(top[i, j], result[i, j]); + } + else if (i >= top.RowCount && j >= top.ColumnCount) + { + AssertHelpers.AreEqual(bottom[i - top.RowCount, j - top.ColumnCount], result[i, j]); + } + else + { + AssertHelpers.AreEqual(0, result[i, j]); + } + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void DiagonalStackWithResultNullShouldThrowException() + { + var top = TestMatrices["Square3x3"]; + var lower = TestMatrices["Wide2x3"]; + Matrix result = null; + top.DiagonalStack(lower, result); + } + + [Test] + [ExpectedArgumentException] + public void DiagonalStackWithInvalidResultMatrixShouldThrowException() + { + var top = TestMatrices["Square3x3"]; + var lower = TestMatrices["Wide2x3"]; + var result = CreateMatrix(top.RowCount + lower.RowCount + 2, top.ColumnCount + lower.ColumnCount); + top.DiagonalStack(lower, result); + } + + [Test] + public virtual void FrobeniusNorm() + { + var matrix = TestMatrices["Square3x3"]; + AssertHelpers.AlmostEqual(10.8819655930903, matrix.FrobeniusNorm(), 14); + + matrix = TestMatrices["Wide2x3"]; + AssertHelpers.AlmostEqual(5.19052560084774, matrix.FrobeniusNorm(), 14); + + matrix = TestMatrices["Tall3x2"]; + AssertHelpers.AlmostEqual(7.59041159967795, matrix.FrobeniusNorm(), 14); + } + + [Test] + public virtual void InfinityNorm() + { + Matrix matrix = TestMatrices["Square3x3"]; + AssertHelpers.AlmostEqual(16.7777033201323, matrix.InfinityNorm(), 14); + + matrix = TestMatrices["Wide2x3"]; + AssertHelpers.AlmostEqual(7.3514039993641, matrix.InfinityNorm(), 14); + + matrix = TestMatrices["Tall3x2"]; + AssertHelpers.AlmostEqual(10.1023756128209, matrix.InfinityNorm(), 14); + } + + [Test] + public virtual void L1Norm() + { + var matrix = TestMatrices["Square3x3"]; + AssertHelpers.AlmostEqual(12.5401248319437, matrix.L1Norm(), 14); + + matrix = TestMatrices["Wide2x3"]; + AssertHelpers.AlmostEqual(5.86479712463225, matrix.L1Norm(), 14); + + matrix = TestMatrices["Tall3x2"]; + AssertHelpers.AlmostEqual(9.49338601320024, matrix.L1Norm(), 14); + } + + [Test] + public virtual void L2Norm() + { + var matrix = TestMatrices["Square3x3"]; + AssertHelpers.AlmostEqual(10.638175225153, matrix.L2Norm(), 14); + + matrix = TestMatrices["Wide2x3"]; + AssertHelpers.AlmostEqual(5.2058554445283, matrix.L2Norm(), 14); + + matrix = TestMatrices["Tall3x2"]; + AssertHelpers.AlmostEqual(7.35826643761172, matrix.L2Norm(), 14); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/BiCgStabTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/BiCgStabTest.cs new file mode 100644 index 00000000..3b50131b --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/BiCgStabTest.cs @@ -0,0 +1,268 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Iterative +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers; + using LinearAlgebra.Complex.Solvers.Iterative; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.Solvers.Status; + using LinearAlgebra.Generic.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 = SparseMatrix.Identity(100); + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, Complex.One); + + // 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]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = SparseMatrix.Identity(100); + + // Scale it with a funny number + matrix.Multiply(new Complex(System.Math.PI, System.Math.PI)); + + // Create the y vector + Vector y = new DenseVector(matrix.RowCount, Complex.One); + + // 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]).Magnitude.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, Complex.One); + + // 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]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(20)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new BiCgStab(monitor); + + var resultx = solver.Solve(matrixA, 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].Real, bReconstruct[i].Real, 1e-5); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-5); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(20)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new BiCgStab(monitor); + var matrixX = solver.Solve(matrixA, 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].Real, matrixBReconstruct[i, j].Real, 1.0e-5); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-5); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/GpBiCgTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/GpBiCgTest.cs new file mode 100644 index 00000000..97de96f7 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/GpBiCgTest.cs @@ -0,0 +1,270 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Iterative +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers; + using LinearAlgebra.Complex.Solvers.Iterative; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.Solvers.Status; + using LinearAlgebra.Generic.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 = SparseMatrix.Identity(100); + + // 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]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = SparseMatrix.Identity(100); + + // 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]).Magnitude.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((y[i] - z[i]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(20)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new GpBiCg(monitor); + + var resultx = solver.Solve(matrixA, 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].Real, bReconstruct[i].Real, 1e-5); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-5); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(20)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new GpBiCg(monitor); + var matrixX = solver.Solve(matrixA, 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].Real, matrixBReconstruct[i, j].Real, 1.0e-5); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-5); + } + } + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/MlkBiCgStabTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/MlkBiCgStabTest.cs new file mode 100644 index 00000000..290fdc92 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/MlkBiCgStabTest.cs @@ -0,0 +1,266 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Iterative +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers; + using LinearAlgebra.Complex.Solvers.Iterative; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.Solvers.Status; + using LinearAlgebra.Generic.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 = SparseMatrix.Identity(100); + + // 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]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = SparseMatrix.Identity(100); + + // 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]).Magnitude.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((y[i] - z[i]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new MlkBiCgStab(monitor); + + var resultx = solver.Solve(matrixA, 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].Real, bReconstruct[i].Real, 1e-5); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-5); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new MlkBiCgStab(monitor); + var matrixX = solver.Solve(matrixA, 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].Real, matrixBReconstruct[i, j].Real, 1.0e-5); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-5); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/TFQMRTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/TFQMRTest.cs new file mode 100644 index 00000000..532b63bd --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Iterative/TFQMRTest.cs @@ -0,0 +1,268 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Iterative +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers; + using LinearAlgebra.Complex.Solvers.Iterative; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.Solvers.Status; + using LinearAlgebra.Generic.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 = SparseMatrix.Identity(100); + + // 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]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [MultipleAsserts] + public void SolveScaledUnitMatrixAndBackMultiply() + { + // Create the identity matrix + Matrix matrix = SparseMatrix.Identity(100); + + // 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]).Magnitude.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((y[i] - z[i]).Magnitude.IsSmaller(ConvergenceBoundary, 1), "#05-" + i); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(20)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new TFQMR(monitor); + + var resultx = solver.Solve(matrixA, 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].Real, bReconstruct[i].Real, 1e-5); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-5); + } + } + + [Test] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(20)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + + var monitor = new Iterator(new IIterationStopCriterium[] + { + new IterationCountStopCriterium(1000), + new ResidualStopCriterium(1e-10), + }); + var solver = new TFQMR(monitor); + var matrixX = solver.Solve(matrixA, 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].Real, matrixBReconstruct[i, j].Real, 1.0e-5); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1.0e-5); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Solvers/IteratorTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/IteratorTest.cs new file mode 100644 index 00000000..035cab67 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/IteratorTest.cs @@ -0,0 +1,355 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic.Solvers.Status; + using LinearAlgebra.Generic.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/Complex/Solvers/Preconditioners/DiagonalTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/DiagonalTest.cs new file mode 100644 index 00000000..962b37b3 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/DiagonalTest.cs @@ -0,0 +1,34 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Preconditioners +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.Preconditioners; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.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].Real.AlmostEqual(product[i].Real, -Epsilon.Magnitude()), "#02-" + i); + Assert.IsTrue(vector[i].Imaginary.AlmostEqual(product[i].Imaginary, -Epsilon.Magnitude()), "#03-" + i); + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/IluptElementSorterTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/IluptElementSorterTest.cs new file mode 100644 index 00000000..bc74fa5e --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/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.Complex.Solvers.Preconditioners +{ + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.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/Complex/Solvers/Preconditioners/IlutpTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/IlutpTest.cs new file mode 100644 index 00000000..beb3e609 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/IlutpTest.cs @@ -0,0 +1,254 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Preconditioners +{ + using System; + using System.Numerics; + using System.Reflection; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.Preconditioners; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.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].Real.AlmostEqual(product[i].Real, -Epsilon.Magnitude()), "#02-" + i); + Assert.IsTrue(vector[i].Imaginary.AlmostEqual(product[i].Imaginary, -Epsilon.Magnitude()), "#03-" + 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].Magnitude, -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].Magnitude, -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].Real.AlmostEqual(original[i, j].Real, -Epsilon.Magnitude()), "#03-" + i + "-" + j); + Assert.IsTrue(sparseMatrix[i, j].Imaginary.AlmostEqual(original[i, j].Imaginary, -Epsilon.Magnitude()), "#04-" + 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].Real.AlmostEqual(original[i, j].Real, -Epsilon.Magnitude()), "#01-" + i + "-" + j); + Assert.IsTrue(sparseMatrix[i, j].Imaginary.AlmostEqual(original[i, j].Imaginary, -Epsilon.Magnitude()), "#02-" + 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/Complex/Solvers/Preconditioners/IncompleteLUTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/IncompleteLUTest.cs new file mode 100644 index 00000000..e2eb406c --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/IncompleteLUTest.cs @@ -0,0 +1,87 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Preconditioners +{ + using System; + using System.Numerics; + using System.Reflection; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.Preconditioners; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.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].Real.AlmostEqual(product[i].Real, -Epsilon.Magnitude()), "#02-" + i); + Assert.IsTrue(vector[i].Imaginary.AlmostEqual(product[i].Imaginary, -Epsilon.Magnitude()), "#03-" + 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].Real.AlmostEqual(original[i, j].Real, -Epsilon.Magnitude()), "#01-" + i + "-" + j); + Assert.IsTrue(sparseMatrix[i, j].Imaginary.AlmostEqual(original[i, j].Imaginary, -Epsilon.Magnitude()), "#02-" + i + "-" + j); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/PreConditionerTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/PreConditionerTest.cs new file mode 100644 index 00000000..7000c743 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/PreConditionerTest.cs @@ -0,0 +1,128 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Preconditioners +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.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/Complex/Solvers/Preconditioners/UnitPreconditionerTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/UnitPreconditionerTest.cs new file mode 100644 index 00000000..f837a471 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/Preconditioners/UnitPreconditionerTest.cs @@ -0,0 +1,30 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.Preconditioners +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.Preconditioners; + using LinearAlgebra.Generic; + using LinearAlgebra.Generic.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/Complex/Solvers/StopCriterium/DivergenceStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/DivergenceStopCriteriumTest.cs new file mode 100644 index 00000000..d2aa7c8f --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/DivergenceStopCriteriumTest.cs @@ -0,0 +1,234 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.StopCriterium +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic.Solvers.Status; + 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 [] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex((i + 1) * (Increase + 0.1), 0) })); + + 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[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new [] { new Complex((i + 1) * (Increase - 0.01), 0) })); + + 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[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new [] { new Complex((i + 1) * (Increase - 0.01), 0) })); + + Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Status check fail."); + } + + // Now make it fail by throwing in a NaN + criterium.DetermineStatus(Iterations, + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new [] { new Complex(double.NaN, 0) })); + + 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[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(previous, 0) })); + + 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[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(previous, 0) })); + + 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[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(1.0, 0) }), + new DenseVector(new[] { new Complex(double.NaN, 0) })); + + 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/Complex/Solvers/StopCriterium/FailureStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/FailureStopCriteriumTest.cs new file mode 100644 index 00000000..ee26eedb --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/FailureStopCriteriumTest.cs @@ -0,0 +1,134 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.StopCriterium +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic.Solvers.Status; + 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[] { new Complex(1.0, 0), new Complex(1.0, 0), new Complex(2.0, 0) }); + var source = new DenseVector(new[] { new Complex(1001.0, 0), Complex.Zero, new Complex(2003.0, 0) }); + var residual = new DenseVector(new[] { new Complex(1000, 0), new Complex(double.NaN, 0), new Complex(2001, 0) }); + + 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[] { new Complex(1.0, 0), new Complex(1.0, 0), new Complex(double.NaN, 0) }); + var source = new DenseVector(new[] { new Complex(1001.0, 0), Complex.Zero, new Complex(2003.0, 0) }); + var residual = new DenseVector(new[] { new Complex(1000, 0), new Complex(1000, 0), new Complex(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[] { new Complex(3.0, 0), new Complex(2.0, 0), new Complex(1, 0) }); + var source = new DenseVector(new[] { new Complex(1001.0, 0), Complex.Zero, new Complex(2003.0, 0) }); + var residual = new DenseVector(new[] { new Complex(1.0, 0), new Complex(2.0, 0), new Complex(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[] { new Complex(1.0, 0), new Complex(1.0, 0), new Complex(2, 0) }); + var source = new DenseVector(new[] { new Complex(1001.0, 0), Complex.Zero, new Complex(2003.0, 0) }); + var residual = new DenseVector(new[] { new Complex(1000, 0), new Complex(1000, 0), new Complex(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/Complex/Solvers/StopCriterium/IterationCountStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/IterationCountStopCriteriumTest.cs new file mode 100644 index 00000000..9026094b --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/IterationCountStopCriteriumTest.cs @@ -0,0 +1,96 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.StopCriterium +{ + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic.Solvers.Status; + 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/Complex/Solvers/StopCriterium/ResidualStopCriteriumTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/ResidualStopCriteriumTest.cs new file mode 100644 index 00000000..a3768620 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Solvers/StopCriterium/ResidualStopCriteriumTest.cs @@ -0,0 +1,262 @@ +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Solvers.StopCriterium +{ + using System.Numerics; + using LinearAlgebra.Complex; + using LinearAlgebra.Complex.Solvers.StopCriterium; + using LinearAlgebra.Generic.Solvers.Status; + 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[] { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1) }); + var source = new DenseVector(new[] { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(double.NaN, 1) }); + var residual = new DenseVector(new[] { new Complex(1000.0, 1), new Complex(1000.0, 1), new Complex(2001.0, 1) }); + + 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[] { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1) }); + var source = new DenseVector(new[] { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1) }); + var residual = new DenseVector(new[] { new Complex(1000.0, 1), new Complex(double.NaN, 1), new Complex(2001.0, 1) }); + + 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[] { Complex.One, Complex.One, Complex.One }); + var source = new DenseVector(new[] { Complex.One, Complex.One, Complex.One }); + var residual = new DenseVector(new[] { Complex.Zero, Complex.Zero, Complex.Zero }); + + 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[] { new Complex(double.NaN, double.NaN), new Complex(double.NaN, double.NaN), new Complex(double.NaN, double.NaN) }); + + // Set the source values + var source = new DenseVector(new[] { new Complex(1.000, 1), new Complex(1.000, 1), new Complex(2.001, 1) }); + + // Set the residual values + var residual = new DenseVector(new[] { new Complex(0.001, 0), new Complex(0.001, 0), new Complex(0.002, 0) }); + + 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[] { new Complex(0.001, 1), new Complex(0.001, 1), new Complex(0.002, 1) }); + var source = new DenseVector(new[] { new Complex(0.001, 1), new Complex(0.001, 1), new Complex(0.002, 1) }); + var residual = new DenseVector(new[] { new Complex(1.000, 0), new Complex(1.000, 0), new Complex(2.001, 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 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/Complex/SparseMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/SparseMatrixTests.cs new file mode 100644 index 00000000..27161a8e --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/SparseMatrixTests.cs @@ -0,0 +1,164 @@ +// +// 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.Complex +{ + using System.Collections.Generic; + using System.Numerics; + using LinearAlgebra.Generic; + using MbUnit.Framework; + using LinearAlgebra.Complex; + + public class SparseMatrixTests : MatrixTests + { + protected override Matrix CreateMatrix(int rows, int columns) + { + return new SparseMatrix(rows, columns); + } + + protected override Matrix CreateMatrix(Complex[,] data) + { + return new SparseMatrix(data); + } + + protected override Vector CreateVector(int size) + { + return new SparseVector(size); + } + + protected override Vector CreateVector(Complex[] data) + { + return new SparseVector(data); + } + + [Test] + public void CanCreateMatrixFrom1DArray() + { + var testData = new Dictionary> + { + { "Singular3x3", new SparseMatrix(3, 3, new[] { new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(1.0, 1), new Complex(2.0, 1), new Complex(2.0, 1), new Complex(2.0, 1) }) }, + { "Square3x3", new SparseMatrix(3, 3, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(-4.4, 1), new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(5.5, 1), new Complex(-3.3, 1), new Complex(2.2, 1), new Complex(6.6, 1) }) }, + { "Square4x4", new SparseMatrix(4, 4, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(1.0, 1), new Complex(-4.4, 1), new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(2.1, 1), new Complex(5.5, 1), new Complex(-3.3, 1), new Complex(2.2, 1), new Complex(6.2, 1), new Complex(6.6, 1), new Complex(-4.4, 1), new Complex(3.3, 1), new Complex(4.3, 1), new Complex(-7.7, 1) }) }, + { "Tall3x2", new SparseMatrix(3, 2, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(-4.4, 1), new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(5.5, 1) }) }, + { "Wide2x3", new SparseMatrix(2, 3, new[] { new Complex(-1.1, 1), Complex.Zero, new Complex(-2.2, 1), new Complex(1.1, 1), new Complex(-3.3, 1), new Complex(2.2, 1) }) } + }; + + foreach (var name in testData.Keys) + { + Assert.AreEqual(TestMatrices[name], testData[name]); + } + } + + [Test] + public void MatrixFrom2DArrayIsCopy() + { + var matrix = new SparseMatrix(TestData2D["Singular3x3"]); + matrix[0, 0] = 10.0; + AssertHelpers.AreEqual(new Complex(1.0, 1), TestData2D["Singular3x3"][0, 0]); + } + + [Test] + [Row("Singular3x3")] + [Row("Singular3x3")] + [Row("Square3x3")] + [Row("Square4x4")] + [Row("Tall3x2")] + [Row("Wide2x3")] + public void CanCreateMatrixFrom2DArray(string name) + { + 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++) + { + AssertHelpers.AreEqual(TestData2D[name][i, j], matrix[i, j]); + } + } + } + + [Test] + public void CanCreateMatrixWithUniformValues() + { + var matrix = new SparseMatrix(10, 10, new Complex(10.0, 1)); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(matrix[i, j], new Complex(10.0, 1)); + } + } + } + + [Test] + public void CanCreateIdentity() + { + var matrix = SparseMatrix.Identity(5); + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + AssertHelpers.AreEqual(i == j ? Complex.One : Complex.Zero, matrix[i, j]); + } + } + } + + [Test] + [Row(0)] + [Row(-1)] + [ExpectedArgumentException] + public void IdentityFailsWithZeroOrNegativeOrder(int order) + { + SparseMatrix.Identity(order); + } + + [Test] + public void CanCreateHeavySparseMatrix() + { + var matrix = new SparseMatrix(500, 1000); + var nonzero = 0; + var rnd = new System.Random(); + + for (int i = 0; i < matrix.RowCount; i++) + { + for (int j = 0; j < matrix.ColumnCount; j++ ) + { + var value = rnd.Next(10) * rnd.Next(10) * rnd.Next(10) * rnd.Next(10) * rnd.Next(10); + if (value != 0) + { + nonzero++; + } + matrix[i, j] = value; + } + } + + Assert.AreEqual(matrix.NonZerosCount, nonzero); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/SparseVectorTest.TextHandling.cs b/src/UnitTests/LinearAlgebraTests/Complex/SparseVectorTest.TextHandling.cs new file mode 100644 index 00000000..510bf2fc --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/SparseVectorTest.TextHandling.cs @@ -0,0 +1,99 @@ +// +// 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.Complex +{ + using System; + using System.Globalization; + using LinearAlgebra.Complex; + using MbUnit.Framework; + + public class SparseVectorTextHandlingTest + { + [Test] + [Row("2", "(2, 0)")] + [Row("(3)", "(3, 0)")] + [Row("[1,2,3]", "(1, 0),(2, 0),(3, 0)")] + [Row(" [ 1.1 , 2.1 , 3.1 ] ", "(1.1, 0),(2.1, 0),(3.1, 0)")] + [Row(" [ -1.1 , 2.1 , +3.1 ] ", "(-1.1, 0),(2.1, 0),(3.1, 0)")] + [Row(" [1.2,3.4 , 5.6] ", "(1.2, 0),(3.4, 0),(5.6, 0)")] + [Row("[1+1i,2+1i,3+1i]", "(1, 1),(2, 1),(3, 1)")] + [Row(" [ 1.1 + 1i , 2.1+1i , 3.1+1i ] ", "(1.1, 1),(2.1, 1),(3.1, 1)")] + [Row(" [ -1.1 + 1i , 2.1-1i , +3.1+1i ] ", "(-1.1, 1),(2.1, -1),(3.1, 1)")] + [Row(" [1.2+2.3i ,3.4+4.5i , 5.6+ 6.7i] ", "(1.2, 2.3),(3.4, 4.5),(5.6, 6.7)")] + public void CanParseComplexSparseVectorsWithInvariant(string stringToParse, string expectedToString) + { + var formatProvider = CultureInfo.InvariantCulture; + SparseVector vector = SparseVector.Parse(stringToParse, formatProvider); + + Assert.AreEqual(expectedToString, vector.ToString(formatProvider)); + } + + [Test] + [Row(" 1.2 + 1i , 3.4 + 1i , 5.6 + 1i ", "(1.2, 1),(3.4, 1),(5.6, 1)", "en-US")] + [Row(" 1.2 + 1i ; 3.4 + 1i ; 5.6 + 1i ", "(1.2, 1);(3.4, 1);(5.6, 1)", "de-CH")] + [Row(" 1,2 + 1i ; 3,4 + 1i ; 5,6 + 1i ", "(1,2, 1);(3,4, 1);(5,6, 1)", "de-DE")] + public void CanParseComplexSparseVectorsWithCulture(string stringToParse, string expectedToString, string culture) + { + var formatProvider = CultureInfo.GetCultureInfo(culture); + SparseVector vector = SparseVector.Parse(stringToParse, formatProvider); + + Assert.AreEqual(expectedToString, vector.ToString(formatProvider)); + } + + [Test] + [MultipleAsserts] + public void ParseThrowsFormatExceptionIfMissingClosingParen() + { + Assert.Throws(() => SparseVector.Parse("(1")); + Assert.Throws(() => SparseVector.Parse("[1")); + } + + [Test] + [Row(null)] + [Row("")] + [Row(",")] + [Row("1,")] + [Row(",1")] + [Row(",1,2,")] + [Row("1,,2,,3")] + [Row("1e+")] + [Row("1e")] + [Row("()")] + [Row("[ ]")] + public void TryParseReturnsFalseWhenGivenBadValueWithInvariant(string str) + { + SparseVector vector; + var ret = SparseVector.TryParse(str, CultureInfo.InvariantCulture, out vector); + Assert.IsFalse(ret); + Assert.IsNull(vector); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/SparseVectorTest.cs b/src/UnitTests/LinearAlgebraTests/Complex/SparseVectorTest.cs new file mode 100644 index 00000000..cac58748 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/SparseVectorTest.cs @@ -0,0 +1,415 @@ +// +// 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.Complex +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using LinearAlgebra.Generic; + using MbUnit.Framework; + using LinearAlgebra.Complex; + + public class SparseVectorTest : VectorTests + { + protected override Vector CreateVector(int size) + { + return new SparseVector(size); + } + + protected override Vector CreateVector(IList data) + { + var vector = new SparseVector(data.Count); + for (var index = 0; index < data.Count; index++) + { + vector[index] = data[index]; + } + return vector; + } + + #region Test similar to DenseVector + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromArray() + { + var data = new Complex[Data.Length]; + Array.Copy(Data, data, Data.Length); + var vector = new SparseVector(data); + + for (var i = 0; i < data.Length; i++) + { + AssertHelpers.AreEqual(data[i], vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromAnotherSparseVector() + { + var vector = new SparseVector(Data); + var other = new SparseVector(vector); + + Assert.AreNotSame(vector, other); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromAnotherVector() + { + var vector = (Vector)new SparseVector(Data); + var other = new SparseVector(vector); + + Assert.AreNotSame(vector, other); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromUserDefinedVector() + { + var vector = new UserDefinedVector(Data); + var other = new SparseVector(vector); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + public void CanCreateSparseVectorWithConstantValues() + { + var vector = new SparseVector(5, new Complex(5, 1)); + Assert.ForAll(vector, value => value == new Complex(5,1)); + } + + [Test] + [MultipleAsserts] + public void CanCreateSparseMatrix() + { + var vector = new SparseVector(3); + var matrix = vector.CreateMatrix(2, 3); + Assert.AreEqual(2, matrix.RowCount); + Assert.AreEqual(3, matrix.ColumnCount); + } + + + [Test] + [MultipleAsserts] + public void CanConvertSparseVectorToArray() + { + var vector = new SparseVector(Data); + var array = vector.ToArray(); + Assert.IsInstanceOfType(typeof(Complex[]), array); + Assert.AreElementsEqual(vector, array); + } + + [Test] + [MultipleAsserts] + public void CanConvertArrayToSparseVector() + { + var array = new[] { Complex.Zero, new Complex(1.0, 1), new Complex(2.0, 1), new Complex(3.0, 1), new Complex(4.0, 1) }; + var vector = new SparseVector(array); + Assert.IsInstanceOfType(typeof(SparseVector), vector); + Assert.AreElementsEqual(array, array); + } + + [Test] + public void CanCallUnaryPlusOperatorOnSparseVector() + { + var vector = new SparseVector(Data); + var other = +vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddTwoSparseVectorsUsingOperator() + { + var vector = new SparseVector(Data); + var other = new SparseVector(Data); + var result = vector + other; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(Data[i] * 2.0, result[i]); + } + } + + [Test] + public void CanCallUnaryNegationOperatorOnSparseVector() + { + var vector = new SparseVector(Data); + var other = -vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(-Data[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractTwoSparseVectorsUsingOperator() + { + var vector = new SparseVector(Data); + var other = new SparseVector(Data); + var result = vector - other; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(Complex.Zero, result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanMultiplySparseVectorByComplexUsingOperators() + { + var vector = new SparseVector(Data); + vector = vector * new Complex(2.0, 1); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + + vector = vector * 1.0; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + + vector = new SparseVector(Data); + vector = new Complex(2.0, 1) * vector; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + + vector = 1.0 * vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * new Complex(2.0, 1), vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanDivideSparseVectorByComplexUsingOperators() + { + var vector = new SparseVector(Data); + vector = vector / new Complex(2.0, 1); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / new Complex(2.0, 1), vector[i]); + } + + vector = vector / 1.0; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / new Complex(2.0, 1), vector[i]); + } + } + + [Test] + public void CanCalculateOuterProductForSparseVector() + { + var vector1 = CreateVector(Data); + var vector2 = CreateVector(Data); + Matrix m = Vector.OuterProduct(vector1, vector2); + for (var i = 0; i < vector1.Count; i++) + { + for (var j = 0; j < vector2.Count; j++) + { + AssertHelpers.AreEqual(m[i, j], vector1[i] * vector2[j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void OuterProducForSparseVectortWithFirstParameterNullShouldThrowException() + { + SparseVector vector1 = null; + var vector2 = CreateVector(Data); + Vector.OuterProduct(vector1, vector2); + } + + [Test] + [ExpectedArgumentNullException] + public void OuterProductForSparseVectorWithSecondParameterNullShouldThrowException() + { + var vector1 = CreateVector(Data); + SparseVector vector2 = null; + Vector.OuterProduct(vector1, vector2); + } + #endregion + + [Test] + [MultipleAsserts] + public void CanCreateSparseVectorFromDenseVector() + { + var vector = (Vector)new DenseVector(Data); + var other = new SparseVector(vector); + + Assert.AreNotSame(vector, other); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CheckSparseMechanismBySettingValues() + { + var vector = new SparseVector(10000); + + //Add non-zero elements + vector[200] = 1.5; + AssertHelpers.AreEqual(1.5, vector[200]); + Assert.AreEqual(1, vector.NonZerosCount); + + vector[500] = 3.5; + AssertHelpers.AreEqual(3.5, vector[500]); + Assert.AreEqual(2, vector.NonZerosCount); + + vector[800] = 5.5; + AssertHelpers.AreEqual(5.5, vector[800]); + Assert.AreEqual(3, vector.NonZerosCount); + + vector[0] = 7.5; + AssertHelpers.AreEqual(7.5, vector[0]); + Assert.AreEqual(4, vector.NonZerosCount); + + //Remove non-zero elements + vector[200] = 0; + AssertHelpers.AreEqual(0, vector[200]); + Assert.AreEqual(3, vector.NonZerosCount); + + vector[500] = 0; + AssertHelpers.AreEqual(0, vector[500]); + Assert.AreEqual(2, vector.NonZerosCount); + + vector[800] = 0; + AssertHelpers.AreEqual(0, vector[800]); + Assert.AreEqual(1, vector.NonZerosCount); + + vector[0] = 0; + AssertHelpers.AreEqual(0, vector[0]); + Assert.AreEqual(0, vector.NonZerosCount); + } + + [Test] + [MultipleAsserts] + public void CheckSparseMechanismByZeroMultiply() + { + var vector = new SparseVector(10000); + + //Add non-zero elements + vector[200] = 1.5; + vector[500] = 3.5; + vector[800] = 5.5; + vector[0] = 7.5; + + //Multiply by 0 + vector *= 0; + AssertHelpers.AreEqual(0, vector[200]); + AssertHelpers.AreEqual(0, vector[500]); + AssertHelpers.AreEqual(0, vector[800]); + AssertHelpers.AreEqual(0, vector[0]); + Assert.AreEqual(0, vector.NonZerosCount); + } + + + [Test] + public void CanDotProductOfTwoSparseVectors() + { + var vectorA = new SparseVector(10000); + vectorA[200] = 1; + vectorA[500] = 3; + vectorA[800] = 5; + vectorA[100] = 7; + vectorA[900] = 9; + + var vectorB = new SparseVector(10000); + vectorB[300] = 3; + vectorB[500] = 5; + vectorB[800] = 7; + + + AssertHelpers.AreEqual(50.0, vectorA.DotProduct(vectorB)); + } + + [Test] + public void CreateHugeSparseVector() + { + var data = new Complex[1000000]; + var rnd = new Random(); + for (var i = 0; i < 1000000; i++) + data[i] = new Complex(rnd.Next(), rnd.Next()); + + var vector = new SparseVector(data); + } + + [Test] + public void PointwiseMultiplySparseVector() + { + var zeroArray = new[] { Complex.Zero, new Complex(1.0, 1), Complex.Zero, new Complex(1.0, 1), Complex.Zero }; + var vector1 = new SparseVector(Data); + var vector2 = new SparseVector(zeroArray); + var result = new SparseVector(vector1.Count); + + vector1.PointwiseMultiply(vector2, result); + + for (var i = 0; i < vector1.Count; i++) + { + AssertHelpers.AreEqual(Data[i] * zeroArray[i], result[i]); + } + Assert.AreEqual(2, result.NonZerosCount); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/UserDefinedMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/UserDefinedMatrixTests.cs new file mode 100644 index 00000000..18a28c15 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/UserDefinedMatrixTests.cs @@ -0,0 +1,215 @@ +// +// 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.Complex +{ + using System; + using System.Numerics; + using Distributions; + using LinearAlgebra.Generic; + using Threading; + + internal class UserDefinedMatrix : Matrix + { + private readonly Complex[,] _data; + + public UserDefinedMatrix(int order): base(order, order) + { + _data = new Complex[order, order]; + } + + public UserDefinedMatrix(int rows, int columns) : base(rows, columns) + { + _data = new Complex[rows, columns]; + } + + public UserDefinedMatrix(Complex[,] data) : base(data.GetLength(0), data.GetLength(1)) + { + _data = (Complex[,])data.Clone(); + } + + public override Complex At(int row, int column) + { + return _data[row, column]; + } + + public override void At(int row, int column, Complex value) + { + _data[row, column] = value; + } + + public override Matrix CreateMatrix(int numberOfRows, int numberOfColumns) + { + return new UserDefinedMatrix(numberOfRows, numberOfColumns); + } + + public override Vector CreateVector(int size) + { + return new UserDefinedVector(size); + } + + public static UserDefinedMatrix Identity(int order) + { + var m = new UserDefinedMatrix(order, order); + for (var i = 0; i < order; i++) + { + m[i, i] = 1.0; + } + + return m; + } + + public override void Negate() + { + Multiply(-Complex.One); + } + + public override Matrix Random(int numberOfRows, int numberOfColumns, IContinuousDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException("numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException("numberOfColumns"); + } + + var matrix = CreateMatrix(numberOfRows, numberOfColumns); + CommonParallel.For( + 0, + ColumnCount, + j => + { + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, j] = new Complex(distribution.Sample(), distribution.Sample()); + } + }); + + return matrix; + } + + public override Matrix Random(int numberOfRows, int numberOfColumns, IDiscreteDistribution distribution) + { + if (numberOfRows < 1) + { + throw new ArgumentException("numberOfRows"); + } + + if (numberOfColumns < 1) + { + throw new ArgumentException("numberOfColumns"); + } + + var matrix = CreateMatrix(numberOfRows, numberOfColumns); + CommonParallel.For( + 0, + ColumnCount, + j => + { + for (var i = 0; i < matrix.RowCount; i++) + { + matrix[i, j] = new Complex(distribution.Sample(), distribution.Sample()); + } + }); + + return matrix; + } + + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + protected sealed override Complex SubtractT(Complex val1, Complex val2) + { + return val1 - val2; + } + + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + protected sealed override Complex DivideT(Complex val1, Complex val2) + { + return val1 / val2; + } + + protected sealed override bool IsOneT(Complex val1) + { + return Complex.One.AlmostEqual(val1); + } + + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + + public override Matrix ConjugateTranspose() + { + var ret = CreateMatrix(ColumnCount, RowCount); + for (var j = 0; j < ColumnCount; j++) + { + for (var i = 0; i < RowCount; i++) + { + ret.At(j, i, At(i, j).Conjugate()); + } + } + + return ret; + } + } + + public class UserDefinedMatrixTests : MatrixTests + { + protected override Matrix CreateMatrix(int rows, int columns) + { + return new UserDefinedMatrix(rows, columns); + } + + protected override Matrix CreateMatrix(Complex[,] data) + { + return new UserDefinedMatrix(data); + } + + protected override Vector CreateVector(int size) + { + return new UserDefinedVector(size); + } + + protected override Vector CreateVector(Complex[] data) + { + return new UserDefinedVector(data); + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Complex/UserDefinedVectorTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/UserDefinedVectorTests.cs new file mode 100644 index 00000000..2675ebe4 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/UserDefinedVectorTests.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.UnitTests.LinearAlgebraTests.Complex +{ + using System; + using System.Numerics; + using System.Collections.Generic; + using Distributions; + using LinearAlgebra.Generic; + using Threading; + + internal class UserDefinedVector : Vector + { + private readonly Complex[] _data; + + public UserDefinedVector(int size) + : base(size) + { + _data = new Complex[size]; + } + + public UserDefinedVector(Complex[] data) + : base(data.Length) + { + _data = (Complex[])data.Clone(); + } + + public override Complex this[int index] + { + get + { + return _data[index]; + } + + set + { + _data[index] = value; + } + } + + public override Matrix CreateMatrix(int rows, int columns) + { + return new UserDefinedMatrix(rows, columns); + } + + public override Vector CreateVector(int size) + { + return new UserDefinedVector(size); + } + + public override Vector Negate() + { + var result = new UserDefinedVector(Count); + CommonParallel.For( + 0, + _data.Length, + index => result[index] = -_data[index]); + + return result; + } + + public override Vector Random(int length, IContinuousDistribution randomDistribution) + { + if (length < 1) + { + throw new ArgumentException("length"); + } + + var v = (UserDefinedVector)CreateVector(length); + for (var index = 0; index < v._data.Length; index++) + { + v._data[index] = new Complex(randomDistribution.Sample(), randomDistribution.Sample()); + } + + return v; + } + + public override Vector Random(int length, IDiscreteDistribution randomDistribution) + { + if (length < 1) + { + throw new ArgumentException("length"); + } + + var v = (UserDefinedVector)CreateVector(length); + for (var index = 0; index < v._data.Length; index++) + { + v._data[index] = new Complex(randomDistribution.Sample(), randomDistribution.Sample()); + } + + return v; + } + + public override int MinimumIndex() + { + throw new NotSupportedException(); + } + + public override int MaximumIndex() + { + throw new NotSupportedException(); + } + + public override Vector Normalize(double p) + { + if (p < 0.0) + { + throw new ArgumentOutOfRangeException("p"); + } + + var norm = Norm(p); + var clone = Clone(); + if (norm == 0.0) + { + return clone; + } + + clone.Multiply(1.0 / norm, clone); + + return clone; + } + + protected sealed override Complex AddT(Complex val1, Complex val2) + { + return val1 + val2; + } + + protected sealed override Complex SubtractT(Complex val1, Complex val2) + { + return val1 - val2; + } + + protected sealed override Complex MultiplyT(Complex val1, Complex val2) + { + return val1 * val2; + } + + protected sealed override Complex DivideT(Complex val1, Complex val2) + { + return val1 / val2; + } + + protected sealed override bool IsOneT(Complex val1) + { + return Complex.One.AlmostEqual(val1); + } + + protected sealed override double AbsoluteT(Complex val1) + { + return val1.Magnitude; + } + + public override void Conjugate(Vector target) + { + if (target == null) + { + throw new ArgumentNullException("target"); + } + + if (Count != target.Count) + { + throw new ArgumentException("target"); + } + + if (ReferenceEquals(this, target)) + { + var tmp = CreateVector(Count); + Conjugate(tmp); + tmp.CopyTo(target); + } + + CommonParallel.For( + 0, + Count, + index => target[index] = this[index].Conjugate()); + } + } + + public class UserDefinedVectorTests : VectorTests + { + protected override Vector CreateVector(int size) + { + return new UserDefinedVector(size); + } + + protected override Vector CreateVector(IList data) + { + var vector = new UserDefinedVector(data.Count); + for (var index = 0; index < data.Count; index++) + { + vector[index] = data[index]; + } + + return vector; + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.Arithmetic.cs b/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.Arithmetic.cs new file mode 100644 index 00000000..b3643e79 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.Arithmetic.cs @@ -0,0 +1,902 @@ +// +// 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.Complex +{ + using System; + using System.Numerics; + using LinearAlgebra.Generic; + using MbUnit.Framework; + + public abstract partial class VectorTests + { + [Test] + public void CanCallPlus() + { + var vector = CreateVector(Data); + var other = vector.Plus(); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + public void OperatorPlusThrowsArgumentNullExceptionWhenCallOnNullVector() + { + Vector vector = null; + Vector other = null; + Assert.Throws(() => other = +vector); + } + + [Test] + public void CanCallUnaryPlusOperator() + { + var vector = CreateVector(Data); + var other = +vector; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddScalarToVector() + { + var copy = CreateVector(Data); + var vector = copy.Add(2.0); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] + 2.0, vector[i]); + } + + vector.Add(0.0); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] + 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddScalarToVectorUsingResultVector() + { + var vector = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Add(2.0, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i] + 2.0, result[i]); + } + + vector.Add(0.0, result); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], result[i]); + } + } + + [Test] + public void ThrowsArgumentNullExceptionWhenAddingScalarWithNullResultVector() + { + var vector = CreateVector(Data.Length); + Assert.Throws(() => vector.Add(0.0, null)); + } + + [Test] + public void ThrowsArgumentExceptionWhenAddingScalarWithWrongSizeResultVector() + { + var vector = CreateVector(Data.Length); + var result = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Add(0.0, result)); + } + + [Test] + public void ThrowsArgumentNullExceptionWhenAddingTwoVectorsAndOneIsNull() + { + var vector = CreateVector(Data); + Assert.Throws(() => vector.Add(null)); + } + + [Test] + public void ThrowsArgumentExceptionWhenAddingTwoVectorsOfDifferingSize() + { + var vector = CreateVector(Data.Length); + var other = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Add(other)); + } + + [Test] + public void ThrowsArgumentNullExceptionWhenAddingTwoVectorsAndResultIsNull() + { + var vector = CreateVector(Data.Length); + var other = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Add(other, null)); + } + + [Test] + public void ThrowsArgumentExceptionWhenAddingTwoVectorsAndResultIsDifferentSize() + { + var vector = CreateVector(Data.Length); + var other = CreateVector(Data.Length); + var result = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Add(other, result)); + } + + [Test] + public void AdditionOperatorThrowsArgumentNullExpectionIfAVectorIsNull() + { + Vector a = null; + var b = CreateVector(Data.Length); + Assert.Throws(() => a += b); + + a = b; + b = null; + Assert.Throws(() => a += b); + } + + [Test] + public void AdditionOperatorThrowsArgumentExpectionIfVectorsAreDifferentSize() + { + var a = CreateVector(Data.Length); + var b = CreateVector(Data.Length + 1); + Assert.Throws(() => a += b); + } + + [Test] + public void CanAddTwoVectors() + { + var copy = CreateVector(Data); + var other = CreateVector(Data); + var vector = copy.Add(other); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddTwoVectorsUsingResultVector() + { + var vector = CreateVector(Data); + var other = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Add(other, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(Data[i] * 2.0, result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddTwoVectorsUsingOperator() + { + var vector = CreateVector(Data); + var other = CreateVector(Data); + var result = vector + other; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(Data[i] * 2.0, result[i]); + } + } + + [Test] + public void CanAddVectorToItself() + { + var copy = CreateVector(Data); + var vector = copy.Add(copy); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddVectorToItselfUsingResultVector() + { + var vector = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Add(vector, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i] * 2.0, result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanAddTwoVectorsUsingItselfAsResultVector() + { + var vector = CreateVector(Data); + var other = CreateVector(Data); + vector.Add(other, vector); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + } + + [Test] + public void CanCallNegate() + { + var vector = CreateVector(Data); + var other = vector.Negate(); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(-Data[i], other[i]); + } + } + + [Test] + public void OperatorNegateThrowsArgumentNullExceptionWhenCallOnNullVector() + { + Vector vector = null; + Vector other = null; + Assert.Throws(() => other = -vector); + } + + [Test] + public void CanCallUnaryNegationOperator() + { + var vector = CreateVector(Data); + var other = -vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(-Data[i], other[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractScalarFromVector() + { + var copy = CreateVector(Data); + var vector = copy.Subtract(2.0); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] - 2.0, vector[i]); + } + + vector.Subtract(0.0); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] - 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractScalarFromVectorUsingResultVector() + { + var vector = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Subtract(2.0, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i] - 2.0, result[i]); + } + + vector.Subtract(0.0, result); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], result[i]); + } + } + + [Test] + public void ThrowsArgumentNullExceptionWhenSubtractingScalarWithNullResultVector() + { + var vector = CreateVector(Data.Length); + Assert.Throws(() => vector.Subtract(0.0, null)); + } + + [Test] + public void ThrowsArgumentExceptionWhenSubtractingScalarWithWrongSizeResultVector() + { + var vector = CreateVector(Data.Length); + var result = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Subtract(0.0, result)); + } + + [Test] + public void ThrowsArgumentNullExceptionWhenSubtractingTwoVectorsAndOneIsNull() + { + var vector = CreateVector(Data); + Assert.Throws(() => vector.Subtract(null)); + } + + [Test] + public void ThrowsArgumentExceptionWhenSubtractingTwoVectorsOfDifferingSize() + { + var vector = CreateVector(Data.Length); + var other = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Subtract(other)); + } + + [Test] + public void ThrowsArgumentNullExceptionWhenSubtractingTwoVectorsAndResultIsNull() + { + var vector = CreateVector(Data.Length); + var other = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Subtract(other, null)); + } + + [Test] + public void ThrowsArgumentExceptionWhenSubtractingTwoVectorsAndResultIsDifferentSize() + { + var vector = CreateVector(Data.Length); + var other = CreateVector(Data.Length); + var result = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Subtract(other, result)); + } + + [Test] + public void SubtractionOperatorThrowsArgumentNullExpectionIfAVectorIsNull() + { + Vector a = null; + var b = CreateVector(Data.Length); + Assert.Throws(() => a -= b); + + a = b; + b = null; + Assert.Throws(() => a -= b); + } + + [Test] + public void SubtractionOperatorThrowsArgumentExpectionIfVectorsAreDifferentSize() + { + var a = CreateVector(Data.Length); + var b = CreateVector(Data.Length + 1); + Assert.Throws(() => a -= b); + } + + [Test] + public void CanSubtractTwoVectors() + { + var copy = CreateVector(Data); + var other = CreateVector(Data); + var vector = copy.Subtract(other); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(0.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractTwoVectorsUsingResultVector() + { + var vector = CreateVector(Data); + var other = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Subtract(other, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(0.0, result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractTwoVectorsUsingOperator() + { + var vector = CreateVector(Data); + var other = CreateVector(Data); + var result = vector - other; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(0.0, result[i]); + } + } + + [Test] + public void CanSubtractVectorFromItself() + { + var copy = CreateVector(Data); + var vector = copy.Subtract(copy); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(0.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractVectorFromItselfUsingResultVector() + { + var vector = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Subtract(vector, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(0.0, result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanSubtractTwoVectorsUsingItselfAsResultVector() + { + var vector = CreateVector(Data); + var other = CreateVector(Data); + vector.Subtract(other, vector); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], other[i]); + AssertHelpers.AreEqual(0.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanDivideVectorByScalar() + { + var copy = CreateVector(Data); + var vector = copy.Divide(2.0); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / 2.0, vector[i]); + } + + vector.Divide(1.0); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanDivideVectorByScalarUsingResultVector() + { + var vector = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Divide(2.0, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i] / 2.0, result[i]); + } + + vector.Divide(1.0, result); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], result[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanMultiplyVectorByScalar() + { + var copy = CreateVector(Data); + var vector = copy.Multiply(2.0); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + + vector.Multiply(1.0); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanMultiplyVectorByScalarUsingResultVector() + { + var vector = CreateVector(Data); + var result = CreateVector(Data.Length); + vector.Multiply(2.0, result); + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], vector[i]); + AssertHelpers.AreEqual(Data[i] * 2.0, result[i]); + } + + vector.Multiply(1.0, result); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i], result[i]); + } + } + + [Test] + public void ThrowsArgumentNullExceptionWhenMultiplyingScalarWithNullResultVector() + { + var vector = CreateVector(Data.Length); + Assert.Throws(() => vector.Multiply(1.0, null)); + } + + [Test] + public void ThrowsArgumentNullExceptionWhenDividingScalarWithNullResultVector() + { + var vector = CreateVector(Data.Length); + Assert.Throws(() => vector.Divide(1.0, null)); + } + + [Test] + public void ThrowsArgumentExceptionWhenMultiplyingScalarWithWrongSizeResultVector() + { + var vector = CreateVector(Data.Length); + var result = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Multiply(0.0, result)); + } + + [Test] + public void ThrowsArgumentExceptionWhenDividingScalarWithWrongSizeResultVector() + { + var vector = CreateVector(Data.Length); + var result = CreateVector(Data.Length + 1); + Assert.Throws(() => vector.Divide(0.0, result)); + } + + [Test] + [MultipleAsserts] + public void CanMultiplyVectorByScalarUsingOperators() + { + var vector = CreateVector(Data); + vector = vector * 2.0; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + + vector = vector * 1.0; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + + vector = CreateVector(Data); + vector = 2.0 * vector; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + + vector = 1.0 * vector; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] * 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void CanDivideVectorByScalarUsingOperators() + { + var vector = CreateVector(Data); + vector = vector / 2.0; + + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / 2.0, vector[i]); + } + + vector = vector / 1.0; + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(Data[i] / 2.0, vector[i]); + } + } + + [Test] + [MultipleAsserts] + public void OperatorMultiplyThrowsArgumentNullExceptionWhenVectorIsNull() + { + Vector vector = null; + Vector result = null; + Assert.Throws(() => result = vector * 2.0); + Assert.Throws(() => result = 2.0 * vector); + } + + [Test] + public void OperatorDivideThrowsArgumentNullExceptionWhenVectorIsNull() + { + Vector vector = null; + Assert.Throws(() => vector = vector / 2.0); + } + + [Test] + public void CanDotProduct() + { + var dataA = CreateVector(Data); + var dataB = CreateVector(Data); + + AssertHelpers.AreEqual(new Complex(50, 30), dataA.DotProduct(dataB)); + } + + [Test] + [ExpectedArgumentNullException] + public void DotProductThrowsExceptionWhenArgumentIsNull() + { + var dataA = CreateVector(Data); + Vector dataB = null; + + dataA.DotProduct(dataB); + } + + [Test] + [ExpectedArgumentException] + public void DotProductThrowsExceptionWhenArgumentHasDifferentSize() + { + var dataA = CreateVector(Data); + var dataB = CreateVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1), new Complex(4, 1), new Complex(5, 1), new Complex(6, 1) }); + + dataA.DotProduct(dataB); + } + + [Test] + public void CanDotProductUsingOperator() + { + var dataA = CreateVector(Data); + var dataB = CreateVector(Data); + + AssertHelpers.AreEqual(new Complex(50, 30), dataA * dataB); + } + + [Test] + [ExpectedArgumentNullException] + public void OperatorDotProductThrowsExceptionWhenLeftArgumentIsNull() + { + var dataA = CreateVector(Data); + Vector dataB = null; + + var d = dataA * dataB; + } + + [Test] + [ExpectedArgumentNullException] + public void OperatorDotProductThrowsExceptionWhenRightArgumentIsNull() + { + Vector dataA = null; + var dataB = CreateVector(Data); + + var d = dataA * dataB; + } + + [Test] + [ExpectedArgumentException] + public void OperatorDotProductThrowsExceptionWhenArgumentHasDifferentSize() + { + var dataA = CreateVector(Data); + var dataB = CreateVector(new[] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1), new Complex(4, 1), new Complex(5, 1), new Complex(6, 1) }); + + var d = dataA * dataB; + } + + [Test] + public void PointwiseMultiply() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + var result = vector1.PointwiseMultiply(vector2); + for (var i = 0; i < vector1.Count; i++) + { + AssertHelpers.AreEqual(Data[i] * Data[i], result[i]); + } + } + + [Test] + [ExpectedArgumentNullException] + public void PointwiseMultiplyWithOtherNullShouldThrowException() + { + var vector1 = CreateVector(Data); + Vector vector2 = null; + var result = CreateVector(vector1.Count); + vector1.PointwiseMultiply(vector2, result); + } + + [Test] + [ExpectedArgumentNullException] + public void PointwiseMultiplyWithResultNullShouldThrowException() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + Vector result = null; + vector1.PointwiseMultiply(vector2, result); + } + + [Test] + [ExpectedArgumentException] + public void PointwiseMultiplyWithInvalidResultLengthShouldThrowException() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + var result = CreateVector(vector1.Count + 1); + vector1.PointwiseMultiply(vector2, result); + } + + [Test] + public void PointwiseMultiplyUsingResultVector() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + var result = CreateVector(vector1.Count); + vector1.PointwiseMultiply(vector2, result); + for (var i = 0; i < vector1.Count; i++) + { + Assert.AreEqual(Data[i] * Data[i], result[i]); + } + } + + [Test] + public void PointWiseDivide() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + var result = vector1.PointwiseDivide(vector2); + for (var i = 0; i < vector1.Count; i++) + { + AssertHelpers.AreEqual(Data[i] / Data[i], result[i]); + } + } + + [Test] + [ExpectedArgumentNullException] + public void PointwiseDivideWithOtherNullShouldThrowException() + { + var vector1 = CreateVector(Data); + Vector vector2 = null; + var result = CreateVector(vector1.Count); + vector1.PointwiseDivide(vector2, result); + } + + [Test] + [ExpectedArgumentNullException] + public void PointwiseDivideWithResultNullShouldThrowException() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + Vector result = null; + vector1.PointwiseDivide(vector2, result); + } + + [Test] + [ExpectedArgumentException] + public void PointwiseDivideWithInvalidResultLengthShouldThrowException() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + var result = CreateVector(vector1.Count + 1); + vector1.PointwiseDivide(vector2, result); + } + + [Test] + public void PointWiseDivideUsingResultVector() + { + var vector1 = CreateVector(Data); + var vector2 = vector1.Clone(); + var result = CreateVector(vector1.Count); + vector1.PointwiseDivide(vector2, result); + for (var i = 0; i < vector1.Count; i++) + { + AssertHelpers.AreEqual(Data[i] / Data[i], result[i]); + } + } + + [Test] + public void CanCalculateOuterProduct() + { + var vector1 = CreateVector(Data); + var vector2 = CreateVector(Data); + Matrix m = Vector.OuterProduct(vector1, vector2); + for (var i = 0; i < vector1.Count; i++) + { + for (var j = 0; j < vector2.Count; j++) + { + AssertHelpers.AreEqual(m[i, j], vector1[i] * vector2[j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void OuterProductWithFirstParameterNullShouldThrowException() + { + Vector vector1 = null; + var vector2 = CreateVector(Data); + Vector.OuterProduct(vector1, vector2); + } + + [Test] + [ExpectedArgumentNullException] + public void OutercProductWithSecondParameterNullShouldThrowException() + { + var vector1 = CreateVector(Data); + Vector vector2 = null; + Vector.OuterProduct(vector1, vector2); + } + + [Test] + public void CanCalculateTensorMultiply() + { + var vector1 = CreateVector(Data); + var vector2 = CreateVector(Data); + var m = vector1.TensorMultiply(vector2); + for (var i = 0; i < vector1.Count; i++) + { + for (var j = 0; j < vector2.Count; j++) + { + AssertHelpers.AreEqual(m[i, j], vector1[i] * vector2[j]); + } + } + } + + [Test] + [ExpectedArgumentNullException] + public void TensorMultiplyWithNullParameterNullShouldThrowException() + { + var vector1 = CreateVector(Data); + Vector vector2 = null; + vector1.TensorMultiply(vector2); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.Norm.cs b/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.Norm.cs new file mode 100644 index 00000000..9bea1b72 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.Norm.cs @@ -0,0 +1,91 @@ +// +// 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.Complex +{ + using System; + using System.Numerics; + using MbUnit.Framework; + + public abstract partial class VectorTests + { + [Test] + public void CanComputeNorm() + { + var vector = CreateVector(Data); + AssertHelpers.AlmostEqual(7.74596669241483, vector.Norm(2), 15); + } + + [Test] + public void CanComputeNorm1() + { + var vector = CreateVector(Data); + AssertHelpers.AlmostEqual(16.0346843392517, vector.Norm(1), 15); + } + + [Test] + public void CanComputeSquareNorm() + { + var vector = CreateVector(Data); + AssertHelpers.AlmostEqual(60, vector.Norm(2) * vector.Norm(2), 15); + } + + [Test] + [Row(1, 16.0346843392517)] + [Row(2, 7.74596669241483)] + [Row(3, 6.28528392332871)] + [Row(10, 5.1608912235454)] + public void CanComputeNormP(int p, double expected) + { + var vector = CreateVector(Data); + AssertHelpers.AlmostEqual(expected, vector.Norm(p), 15); + } + + [Test] + public void CanComputeNormInfinity() + { + var vector = CreateVector(Data); + AssertHelpers.AlmostEqual(5.09901951359279, vector.Norm(Double.PositiveInfinity), 15); + } + + [Test] + [MultipleAsserts] + public void CanNormalizeVector() + { + var vector = CreateVector(Data); + var result = vector.Normalize(2); + AssertHelpers.AlmostEqual(new Complex(0.129099444873581, 0.129099444873581), result[0], 14); + AssertHelpers.AlmostEqual(new Complex(0.258198889747161, 0.129099444873581), result[1], 14); + AssertHelpers.AlmostEqual(new Complex(0.387298334620742, 0.129099444873581), result[2], 14); + AssertHelpers.AlmostEqual(new Complex(0.516397779494322, 0.129099444873581), result[3], 14); + AssertHelpers.AlmostEqual(new Complex(0.645497224367903, 0.129099444873581), result[4], 14); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.cs new file mode 100644 index 00000000..790baf61 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/VectorTests.cs @@ -0,0 +1,398 @@ +// +// 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.Complex +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Numerics; + using Distributions; + using LinearAlgebra.Generic; + using MbUnit.Framework; + + [TestFixture] + public abstract partial class VectorTests + { + protected readonly Complex[] Data = { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1), new Complex(4, 1), new Complex(5, 1) }; + + [Test] + [MultipleAsserts] + public void CanCloneVector() + { + var vector = CreateVector(Data); + var clone = vector.Clone(); + + Assert.AreNotSame(vector, clone); + Assert.AreEqual(vector.Count, clone.Count); + for (var index = 0; index < Data.Length; index++) + { + AssertHelpers.AreEqual(vector[index], clone[index]); + } + } + + [Test] + [MultipleAsserts] + public void CanCloneVectorUsingICloneable() + { + var vector = CreateVector(Data); + var clone = (Vector)((ICloneable)vector).Clone(); + + Assert.AreNotSame(vector, clone); + Assert.AreEqual(vector.Count, clone.Count); + for (var index = 0; index < Data.Length; index++) + { + AssertHelpers.AreEqual(vector[index], clone[index]); + } + } + + [Test] + public void CanConvertVectorToString() + { + var vector = CreateVector(Data); + var str = vector.ToString(); + var sep = CultureInfo.CurrentCulture.TextInfo.ListSeparator; + var decsep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + Assert.AreEqual(string.Format("(1{0} 1){1}(2, 1){1}(3{0} 1){1}(4{0} 1){1}(5{0} 1)", decsep, sep), str); + } + + [Test] + [MultipleAsserts] + public void CanCopyPartialVectorToAnother() + { + var vector = CreateVector(Data); + var other = CreateVector(Data.Length); + + vector.CopyTo(other, 2, 2, 2); + + AssertHelpers.AreEqual(Complex.Zero, other[0]); + AssertHelpers.AreEqual(Complex.Zero, other[1]); + AssertHelpers.AreEqual(new Complex(3, 1), other[2]); + AssertHelpers.AreEqual(new Complex(4, 1), other[3]); + AssertHelpers.AreEqual(Complex.Zero, other[4]); + } + + [Test] + [MultipleAsserts] + public void CanCopyPartialVectorToSelf() + { + var vector = CreateVector(Data); + vector.CopyTo(vector, 0, 2, 2); + + AssertHelpers.AreEqual(new Complex(1, 1), vector[0]); + AssertHelpers.AreEqual(new Complex(2, 1), vector[1]); + AssertHelpers.AreEqual(new Complex(1, 1), vector[2]); + AssertHelpers.AreEqual(new Complex(2, 1), vector[3]); + AssertHelpers.AreEqual(new Complex(5, 1), vector[4]); + } + + [Test] + [MultipleAsserts] + public void CanCopyVectorToAnother() + { + var vector = CreateVector(Data); + var other = CreateVector(Data.Length); + + vector.CopyTo(other); + + for (var index = 0; index < Data.Length; index++) + { + AssertHelpers.AreEqual(vector[index], other[index]); + } + } + + [Test] + [Ignore] + public void CanCreateMatrix() + { + } + + [Test] + public void CanCreateVector() + { + var expected = CreateVector(5); + var actual = expected.CreateVector(5); + Assert.AreEqual(expected.GetType(), actual.GetType(), "vectors are same type."); + } + + [Test] + public void CanEnumerateOverVector() + { + var vector = CreateVector(Data); + Assert.AreElementsEqual(Data, vector); + } + + [Test] + [MultipleAsserts] + public void CanEnumerateOverVectorUsingIEnumerable() + { + var enumerable = (IEnumerable)CreateVector(Data); + var index = 0; + foreach (var element in enumerable) + { + AssertHelpers.AreEqual(Data[index++], (Complex)element); + } + } + + [Test] + [MultipleAsserts] + public void CanEquateVectors() + { + var vector1 = CreateVector(Data); + var vector2 = CreateVector(Data); + var vector3 = CreateVector(4); + Assert.IsTrue(vector1.Equals(vector1)); + Assert.IsTrue(vector1.Equals(vector2)); + Assert.IsFalse(vector1.Equals(vector3)); + Assert.IsFalse(vector1.Equals(null)); + } + + [Test] + [MultipleAsserts] + public void ThrowsArgumentExceptionIfSizeIsNotPositive() + { + Assert.Throws(() => CreateVector(-1)); + Assert.Throws(() => CreateVector(0)); + } + + [Test] + public void TestingForEqualityWithNonVectorReturnsFalse() + { + var vector = CreateVector(Data); + Assert.IsFalse(vector.Equals(2)); + } + + [Test] + public void CanTestForEqualityUsingObjectEquals() + { + var vector1 = CreateVector(Data); + var vector2 = CreateVector(Data); + Assert.IsTrue(vector1.Equals((object)vector2)); + } + + [Test] + public void VectorGetHashCode() + { + var vector = CreateVector(new [] { new Complex(1, 1), new Complex(2, 1), new Complex(3, 1), new Complex(4, 1), new Complex(5, 1) }); + Assert.AreEqual(1104007323, vector.GetHashCode()); + } + + [Test] + public void GetIndexedEnumerator() + { + var vector = CreateVector(Data); + foreach (var pair in vector.GetIndexedEnumerator()) + { + AssertHelpers.AreEqual(Data[pair.Key], pair.Value); + } + } + + [Test] + public void CanConvertVectorToArray() + { + var vector = CreateVector(Data); + var array = vector.ToArray(); + Assert.AreElementsEqual(vector, array); + } + + [Test] + [MultipleAsserts] + public void CanConvertVectorToColumnMatrix() + { + var vector = CreateVector(Data); + var matrix = vector.ToColumnMatrix(); + + Assert.AreEqual(vector.Count, matrix.RowCount); + Assert.AreEqual(1, matrix.ColumnCount); + + for (var i = 0; i < vector.Count; i++) + { + AssertHelpers.AreEqual(vector[i], matrix[i, 0]); + } + } + + [Test] + [MultipleAsserts] + public void CanConvertVectorToRowMatrix() + { + var vector = CreateVector(Data); + var matrix = vector.ToRowMatrix(); + + Assert.AreEqual(vector.Count, matrix.ColumnCount); + Assert.AreEqual(1, matrix.RowCount); + + for (var i = 0; i < vector.Count; i++) + { + AssertHelpers.AreEqual(vector[i], matrix[0, i]); + } + } + + [Test] + public void CanSetValues() + { + var vector = CreateVector(Data); + vector.SetValues(Data); + for (var i = 0; i < Data.Length; i++) + { + AssertHelpers.AreEqual(vector[i], Data[i]); + } + } + + [Test] + [Row(0, 5)] + [Row(2, 2)] + [Row(1, 4)] + [Row(6, 10, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(1, 10, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(1, -10, ExpectedException = typeof(ArgumentOutOfRangeException))] + public void CanCalculateSubVector(int index, int length) + { + var vector = CreateVector(Data); + var sub = vector.SubVector(index, length); + Assert.AreEqual(length, sub.Count); + for (var i = 0; i < length; i++) + { + AssertHelpers.AreEqual(vector[i + index], sub[i]); + } + } + + [Test] + public void CanFindAbsoluteMinimumIndex() + { + var source = CreateVector(Data); + const int Expected = 0; + var actual = source.AbsoluteMinimumIndex(); + Assert.AreEqual(Expected, actual); + } + + [Test] + public void CanFindAbsoluteMinimum() + { + var source = CreateVector(Data); + var expected = new Complex(1, 1).Magnitude; + var actual = source.AbsoluteMinimum(); + AssertHelpers.AreEqual(expected, actual); + } + + [Test] + public void CanFindAbsoluteMaximumIndex() + { + var source = CreateVector(Data); + const int Expected = 4; + var actual = source.AbsoluteMaximumIndex(); + Assert.AreEqual(Expected, actual); + } + + [Test] + public void CanFindAbsoluteMaximum() + { + var source = CreateVector(Data); + var expected = new Complex(5, 1).Magnitude; + var actual = source.AbsoluteMaximum(); + AssertHelpers.AreEqual(expected, actual); + } + + [Test] + [Row(0, 5)] + [Row(2, 2)] + [Row(1, 4)] + [Row(6, 10, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(1, 10, ExpectedException = typeof(ArgumentOutOfRangeException))] + [Row(1, -10, ExpectedException = typeof(ArgumentOutOfRangeException))] + public void CanGetSubVector(int index, int length) + { + var vector = CreateVector(Data); + var sub = vector.SubVector(index, length); + Assert.AreEqual(length, sub.Count); + for (var i = 0; i < length; i++) + { + AssertHelpers.AreEqual(vector[i + index], sub[i]); + } + } + + [Test] + public void CanSum() + { + Complex[] testData = { new Complex(-20, -1), new Complex(-10, -1), new Complex(10, 1), new Complex(20, 1), new Complex(30,1) }; + var vector = CreateVector(testData); + var actual = vector.Sum(); + var expected = new Complex(30, 1); + AssertHelpers.AreEqual(expected, actual); + } + + [Test] + public void CanSumMagnitudes() + { + Complex[] testData = { new Complex(-20, -1), new Complex(-10, -1), new Complex(10, 1), new Complex(20, 1), new Complex(30, 1) }; + var vector = CreateVector(testData); + var actual = vector.SumMagnitudes(); + var expected = testData.Sum(complex => complex.Magnitude); + Assert.AreEqual(expected, actual); + } + + [Test] + [ExpectedArgumentNullException] + public void CanSetValuesWithNullParameterShouldThrowException() + { + var vector = CreateVector(Data); + vector.SetValues(null); + } + + [Test] + [ExpectedArgumentException] + public void CanSetValuesWithNonEqualDataLengthShouldThrowException() + { + var vector = CreateVector(Data.Length + 2); + vector.SetValues(Data); + } + + + [Test] + [ExpectedArgumentException] + public void RandomWithNumberOfElementsLessThanZeroShouldThrowException() + { + var vector = CreateVector(4); + vector.Random(-2, new ContinuousUniform()); + } + + [Test] + public void CanClearVector() + { + Complex[] testData = { new Complex(-20, -1), new Complex(-10, -1), new Complex(10, 1), new Complex(20, 1), new Complex(30, 1) }; + var vector = CreateVector(testData); + vector.Clear(); + foreach (var element in vector) + { + AssertHelpers.AreEqual(Complex.Zero, element); + } + } + + protected abstract Vector CreateVector(int size); + protected abstract Vector CreateVector(IList data); + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/CholeskyTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/CholeskyTests.cs index f7d98a4f..1715701a 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/CholeskyTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/CholeskyTests.cs @@ -30,9 +30,9 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double; - using LinearAlgebra.Double.Factorization; public class CholeskyTests { diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs index 0ecbe6c9..87932471 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs @@ -30,6 +30,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double.Factorization; diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/LUTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/LUTests.cs index 3c8da2af..a556364b 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/LUTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/LUTests.cs @@ -30,10 +30,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double; - using LinearAlgebra.Double.Factorization; - + public class LUTests { [Test] diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/QRTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/QRTests.cs index ac08952d..7f45fded 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/QRTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/QRTests.cs @@ -30,6 +30,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double; using LinearAlgebra.Double.Factorization; diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/SvdTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/SvdTests.cs index fa35945d..93899ccc 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/SvdTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/SvdTests.cs @@ -31,6 +31,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { using System; + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double; using LinearAlgebra.Double.Factorization; diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserCholeskyTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserCholeskyTests.cs index 9840babb..6734c3f1 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserCholeskyTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserCholeskyTests.cs @@ -30,8 +30,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; - using LinearAlgebra.Double.Factorization; public class UserCholeskyTests { diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserLUTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserLUTests.cs index 21d42a5e..ab45f912 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserLUTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserLUTests.cs @@ -30,8 +30,8 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; - using LinearAlgebra.Double.Factorization; public class UserLUTests { diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserQRTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserQRTests.cs index b6a39f30..1c678b6f 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserQRTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserQRTests.cs @@ -30,6 +30,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double.Factorization; diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserSvdTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserSvdTests.cs index 607ca312..50662264 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserSvdTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserSvdTests.cs @@ -31,6 +31,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { using System; + using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double.Factorization; diff --git a/src/UnitTests/LinearAlgebraTests/Double/UserDefinedMatrixTests.cs b/src/UnitTests/LinearAlgebraTests/Double/UserDefinedMatrixTests.cs index c12c7839..7b83c327 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/UserDefinedMatrixTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/UserDefinedMatrixTests.cs @@ -31,7 +31,6 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double { using System; - using System.Text; using Distributions; using LinearAlgebra.Generic; using Properties; @@ -175,6 +174,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double { return Math.Abs(val1); } + + public override Matrix ConjugateTranspose() + { + throw new NotSupportedException(); + } } public class UserDefinedMatrixTests : MatrixTests diff --git a/src/UnitTests/LinearAlgebraTests/Double/UserDefinedVectorTests.cs b/src/UnitTests/LinearAlgebraTests/Double/UserDefinedVectorTests.cs index 1629bf96..5403dc33 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/UserDefinedVectorTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/UserDefinedVectorTests.cs @@ -32,7 +32,6 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double { using System; using System.Collections.Generic; - using System.Text; using Distributions; using LinearAlgebra.Generic; using Properties; @@ -200,6 +199,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double { return Math.Abs(val1); } + + public override void Conjugate(Vector target) + { + throw new NotSupportedException(); + } } public class UserDefinedVectorTests : VectorTests diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 904d3373..dfb5c828 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -90,6 +90,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +