diff --git a/src/MathNet.Numerics.5.1.ReSharper b/src/MathNet.Numerics.5.1.ReSharper index abc2b585..934aa504 100644 --- a/src/MathNet.Numerics.5.1.ReSharper +++ b/src/MathNet.Numerics.5.1.ReSharper @@ -13,7 +13,8 @@ Wishart Wikipedia Marsaglia -Xorshift +Xorshift +λ diff --git a/src/Numerics/Distributions/Discrete/Binomial.cs b/src/Numerics/Distributions/Discrete/Binomial.cs index ce31e666..0e5cc658 100644 --- a/src/Numerics/Distributions/Discrete/Binomial.cs +++ b/src/Numerics/Distributions/Discrete/Binomial.cs @@ -414,7 +414,7 @@ namespace MathNet.Numerics.Distributions } /// - /// Samples an array of Bernoulli distributed random variables. + /// Samples an array of Binomially distributed random variables. /// /// a sequence of successes in N trials. public IEnumerable Samples() diff --git a/src/Numerics/Distributions/Discrete/Poisson.cs b/src/Numerics/Distributions/Discrete/Poisson.cs new file mode 100644 index 00000000..f3fc4b22 --- /dev/null +++ b/src/Numerics/Distributions/Discrete/Poisson.cs @@ -0,0 +1,399 @@ +// +// 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.Distributions +{ + using System; + using System.Collections.Generic; + using Properties; + + /// + /// Pseudo-random generation of poisson distributed deviates. + /// + /// + /// Distribution is described at Wikipedia - Poisson distribution. + /// Knuth's method is used to generate Poisson distributed random variables. + /// f(x) = exp(-λ)*λ^x/x!; + /// + public class Poisson : IDiscreteDistribution + { + /// + /// The Poisson distribution parameter λ. + /// + private double _lambda; + + /// + /// The distribution's random number generator. + /// + private Random _random; + + /// + /// Gets or sets the Poisson distribution parameter λ. + /// + public double Lambda + { + get + { + return _lambda; + } + + set + { + SetParameters(value); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The Poisson distribution parameter λ. + /// If is equal or less then 0.0. + public Poisson(double lambda) + { + SetParameters(lambda); + RandomSource = new Random(); + } + + /// + /// Sets the parameters of the distribution after checking their validity. + /// + /// The mean (λ) of the distribution. + /// When the parameters don't pass the function. + private void SetParameters(double lambda) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(lambda)) + { + throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + } + + _lambda = lambda; + } + + /// + /// Checks whether the parameters of the distribution are valid. + /// + /// The mean (λ) of the distribution. + /// true when the parameters are valid, false otherwise. + private static bool IsValidParameterSet(double lambda) + { + return lambda > 0.00; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return "Poisson(λ = " + _lambda + ")"; + } + + #region IDistribution Members + + /// + /// Gets or sets the random number generator which is used to draw random samples. + /// + public Random RandomSource + { + get + { + return _random; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(); + } + + _random = value; + } + } + + /// + /// Gets the mean of the distribution. + /// + public double Mean + { + get + { + return _lambda; + } + } + + /// + /// Gets the variance of the distribution. + /// + public double Variance + { + get + { + return _lambda; + } + } + + /// + /// Gets the standard deviation of the distribution. + /// + public double StdDev + { + get + { + return Math.Sqrt(_lambda); + } + } + + /// + /// Gets the entropy of the distribution. + /// + /// Approximation, see Wikipedia Poisson distribution + public double Entropy + { + get + { + return (0.5 * Math.Log(2 * Constants.Pi * Constants.E * _lambda)) - (1.0 / (12.0 * _lambda)) - (1.0 / (24.0 * _lambda * _lambda)) - (19.0 / (360.0 * _lambda * _lambda * _lambda)); + } + } + + /// + /// Gets the skewness of the distribution. + /// + public double Skewness + { + get + { + return 1.0 / Math.Sqrt(_lambda); + } + } + + /// + /// Gets the smallest element in the domain of the distributions which can be represented by an integer. + /// + public int Minimum + { + get + { + return 0; + } + } + + /// + /// Gets the largest element in the domain of the distributions which can be represented by an integer. + /// + public int Maximum + { + get + { + return int.MaxValue; + } + } + + /// + /// Computes the cumulative distribution function of the Poisson distribution. + /// + /// The location at which to compute the cumulative density. + /// the cumulative density at . + public double CumulativeDistribution(double x) + { + return 1.0 - SpecialFunctions.GammaLowerRegularized(x + 1, _lambda); + } + + #endregion + + #region IDiscreteDistribution Members + + /// + /// Gets the mode of the distribution. + /// + public int Mode + { + get + { + return (int)Math.Floor(_lambda); + } + } + + /// + /// Gets the median of the distribution. + /// + /// Approximation, see Wikipedia Poisson distribution + public int Median + { + get + { + return (int)Math.Floor(_lambda + (1.0 / 3.0) - (0.02 / _lambda)); + } + } + + /// + /// Computes values of the probability mass function. + /// + /// The location in the domain where we want to evaluate the probability mass function. + /// the probability mass at location . + public double Probability(int k) + { + return Math.Exp(-_lambda + (k * Math.Log(_lambda)) - SpecialFunctions.FactorialLn(k)); + } + + /// + /// Computes values of the log probability mass function. + /// + /// The location in the domain where we want to evaluate the log probability mass function. + /// the log probability mass at location . + public double ProbabilityLn(int k) + { + return -_lambda + (k * Math.Log(_lambda)) - SpecialFunctions.FactorialLn(k); + } + + /// + /// Samples a Poisson distributed random variable. + /// + /// A sample from the Poisson distribution. + public int Sample() + { + return DoSample(RandomSource, _lambda); + } + + /// + /// Samples an array of Poisson distributed random variables. + /// + /// a sequence of successes in N trials. + public IEnumerable Samples() + { + while (true) + { + yield return DoSample(RandomSource, _lambda); + } + } + + #endregion + + /// + /// Samples a Poisson distributed random variable. + /// + /// The random number generator to use. + /// The Poisson distribution parameter λ. + /// A sample from the Poisson distribution. + public static int Sample(Random rnd, double lambda) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(lambda)) + { + throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + } + + return DoSample(rnd, lambda); + } + + /// + /// Samples a sequence of Poisson distributed random variables. + /// + /// The random number generator to use. + /// The Poisson distribution parameter λ. + /// a sequence of samples from the distribution. + public static IEnumerable Samples(Random rnd, double lambda) + { + if (Control.CheckDistributionParameters && !IsValidParameterSet(lambda)) + { + throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + } + + while (true) + { + yield return DoSample(rnd, lambda); + } + } + + /// + /// Generates one sample from the Poisson distribution. + /// + /// The random source to use. + /// The Poisson distribution parameter λ. + /// A random sample from the Poisson distribution. + private static int DoSample(Random rnd, double lambda) + { + return (lambda < 30.0) ? DoSampleShort(rnd, lambda) : DoSampleLarge(rnd, lambda); + } + + /// + /// Generates one sample from the Poisson distribution by Knuth's method. + /// + /// The random source to use. + /// The Poisson distribution parameter λ. + /// A random sample from the Poisson distribution. + private static int DoSampleShort(Random rnd, double lambda) + { + var limit = Math.Exp(-lambda); + var count = 0; + for (var product = rnd.NextDouble(); product >= limit; product *= rnd.NextDouble()) + { + count++; + } + + return count; + } + + /// + /// Generates one sample from the Poisson distribution by "Rejection method PA". + /// + /// The random source to use. + /// The Poisson distribution parameter λ. + /// A random sample from the Poisson distribution. + /// "Rejection method PA" from "The Computer Generation of Poisson Random Variables" by A. C. Atkinson, + /// Journal of the Royal Statistical Society Series C (Applied Statistics) Vol. 28, No. 1. (1979) + /// The article is on pages 29-35. The algorithm given here is on page 32. + private static int DoSampleLarge(Random rnd, double lambda) + { + var c = 0.767 - (3.36 / lambda); + var beta = Math.PI / Math.Sqrt(3.0 * lambda); + var alpha = beta * lambda; + var k = Math.Log(c) - lambda - Math.Log(beta); + + for (;;) + { + var u = rnd.NextDouble(); + var x = (alpha - Math.Log((1.0 - u) / u)) / beta; + var n = (int)Math.Floor(x + 0.5); + if (n < 0) + { + continue; + } + + var v = rnd.NextDouble(); + var y = alpha - (beta * x); + var temp = 1.0 + Math.Exp(y); + var lhs = y + Math.Log(v / (temp * temp)); + var rhs = k + (n * Math.Log(lambda)) - SpecialFunctions.FactorialLn(n); + if (lhs <= rhs) + { + return n; + } + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/DenseVector.cs b/src/Numerics/LinearAlgebra/Complex/DenseVector.cs index cb30a7b9..c6537664 100644 --- a/src/Numerics/LinearAlgebra/Complex/DenseVector.cs +++ b/src/Numerics/LinearAlgebra/Complex/DenseVector.cs @@ -28,6 +28,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex { using System; using System.Collections.Generic; + using System.Linq; using System.Numerics; using Distributions; using Generic; @@ -1185,6 +1186,11 @@ namespace MathNet.Numerics.LinearAlgebra.Complex index => Data[index].Magnitude); } + if (2.0 == p) + { + return Data.Aggregate(Complex.Zero, SpecialFunctions.Hypotenuse).Magnitude; + } + if (Double.IsPositiveInfinity(p)) { return CommonParallel.Select( diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/DenseEvd.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseEvd.cs new file mode 100644 index 00000000..94820088 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseEvd.cs @@ -0,0 +1,964 @@ +// +// 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; + + /// + /// Eigenvalues and eigenvectors of a complex matrix. + /// + /// + /// If A is hermitan, then A = V*D*V' where the eigenvalue matrix D is + /// diagonal and the eigenvector matrix V is hermitan. + /// I.e. A = V*D*V' and V*VH=I. + /// If A is not symmetric, then the eigenvalue matrix D is block diagonal + /// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, + /// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The + /// columns of V represent the eigenvectors in the sense that A*V = V*D, + /// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly + /// conditioned, or even singular, so the validity of the equation + /// A = V*D*Inverse(V) depends upon V.cond(). + /// + public class DenseEvd : Evd + { + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public DenseEvd(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matricies for eigenvalues and eigenvectors + MatrixEv = DenseMatrix.Identity(order); + MatrixD = matrix.CreateMatrix(order, order); + VectorEv = new DenseVector(order); + + IsSymmetric = true; + + for (var i = 0; i < order & IsSymmetric; i++) + { + for (var j = 0; j < order & IsSymmetric; j++) + { + IsSymmetric &= matrix[i, j] == matrix[j, i].Conjugate(); + } + } + + if (IsSymmetric) + { + var matrixCopy = matrix.ToArray(); + var tau = new Complex[order]; + var d = new double[order]; + var e = new double[order]; + + SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + SymmetricDiagonalize(((DenseMatrix)MatrixEv).Data, d, e, order); + SymmetricUntridiagonalize(((DenseMatrix)MatrixEv).Data, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + VectorEv[i] = new Complex(d[i], e[i]); + } + } + else + { + var matrixH = matrix.ToArray(); + NonsymmetricReduceToHessenberg(((DenseMatrix)MatrixEv).Data, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(((DenseVector)VectorEv).Data, ((DenseMatrix)MatrixEv).Data, matrixH, order); + } + + MatrixD.SetDiagonal(VectorEv); + } + + /// + /// Reduces a complex hermitian matrix to a real symmetric tridiagonal matrix using unitary similarity transformations. + /// + /// Source matrix to reduce + /// Output: Arrays for internal storage of real parts of eigenvalues + /// Output: Arrays for internal storage of imaginary parts of eigenvalues + /// Output: Arrays that contains further information about the transformations. + /// Order of initial matrix + /// This is derived from the Algol procedures HTRIDI by + /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricTridiagonalize(Complex[,] matrixA, double[] d, double[] e, Complex[] tau, int order) + { + double hh; + tau[order - 1] = Complex.One; + + for (var i = 0; i < order; i++) + { + d[i] = matrixA[i, i].Real; + } + + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0; + var h = 0.0; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(matrixA[i, k].Real) + Math.Abs(matrixA[i, k].Imaginary); + } + + if (scale == 0.0) + { + tau[i - 1] = Complex.One; + e[i] = 0.0; + } + else + { + for (var k = 0; k < i; k++) + { + matrixA[i, k] /= scale; + h += matrixA[i, k].MagnitudeSquared(); + } + + Complex g = Math.Sqrt(h); + e[i] = scale * g.Real; + + Complex temp; + var f = matrixA[i, i - 1]; + if (f.Magnitude != 0) + { + temp = -(matrixA[i, i - 1].Conjugate() * tau[i].Conjugate()) / f.Magnitude; + h += f.Magnitude * g.Real; + g = 1.0 + (g / f.Magnitude); + matrixA[i, i - 1] *= g; + } + else + { + temp = -tau[i].Conjugate(); + matrixA[i, i - 1] = g; + } + + if ((f.Magnitude == 0) || (i != 1)) + { + f = Complex.Zero; + for (var j = 0; j < i; j++) + { + var tmp = Complex.Zero; + + // Form element of A*U. + for (var k = 0; k <= j; k++) + { + tmp += matrixA[j, k] * matrixA[i, k].Conjugate(); + } + + for (var k = j + 1; k <= i - 1; k++) + { + tmp += matrixA[k, j].Conjugate() * matrixA[i, k].Conjugate(); + } + + // Form element of P + tau[j] = tmp / h; + f += (tmp / h) * matrixA[i, j]; + } + + hh = f.Real / (h + h); + + // Form the reduced A. + for (var j = 0; j < i; j++) + { + f = matrixA[i, j].Conjugate(); + g = tau[j] - (hh * f); + tau[j] = g.Conjugate(); + + for (var k = 0; k <= j; k++) + { + matrixA[j, k] -= (f * tau[k]) + (g * matrixA[i, k]); + } + } + } + + for (var k = 0; k < i; k++) + { + matrixA[i, k] *= scale; + } + + tau[i - 1] = temp.Conjugate(); + } + + hh = d[i]; + d[i] = matrixA[i, i].Real; + matrixA[i, i] = new Complex(hh, scale * Math.Sqrt(h)); + } + + hh = d[0]; + d[0] = matrixA[0, 0].Real; + matrixA[0, 0] = hh; + e[0] = 0.0; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricDiagonalize(Complex[] dataEv, double[] d, double[] e, int order) + { + const int Maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0; + + var f = 0.0; + var tst1 = 0.0; + var eps = Precision.DoubleMachinePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0 * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0; + var s2 = 0.0; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = dataEv[((i + 1) * order) + k].Real; + dataEv[((i + 1) * order) + k] = (s * dataEv[(i * order) + k].Real) + (c * h); + dataEv[(i * order) + k] = (c * dataEv[(i * order) + k].Real) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= Maxiter) + { + throw new ArgumentException(Resources.ConvergenceFailed); + } + } + while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = dataEv[(i * order) + j].Real; + dataEv[(i * order) + j] = dataEv[(k * order) + j]; + dataEv[(k * order) + j] = p; + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// Data array of matrix V (eigenvectors) + /// Previously tridiagonalized matrix by . + /// Contains further information about the transformations + /// Input matrix order + /// This is derived from the Algol procedures HTRIBK, by + /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricUntridiagonalize(Complex[] dataEv, Complex[,] matrixA, Complex[] tau, int order) + { + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = dataEv[(j * order) + i].Real * tau[i].Conjugate(); + } + } + + // Recover and apply the Householder matrices. + for (var i = 1; i < order; i++) + { + var h = matrixA[i, i].Imaginary; + if (h != 0) + { + for (var j = 0; j < order; j++) + { + var s = Complex.Zero; + for (var k = 0; k < i; k++) + { + s += dataEv[(j * order) + k] * matrixA[i, k]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + dataEv[(j * order) + k] -= s * matrixA[i, k].Conjugate(); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + private static void NonsymmetricReduceToHessenberg(Complex[] dataEv, Complex[,] matrixH, int order) + { + var ort = new Complex[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0; + for (var i = m; i < order; i++) + { + scale += Math.Abs(matrixH[i, m - 1].Real) + Math.Abs(matrixH[i, m - 1].Imaginary); + } + + if (scale != 0.0) + { + // Compute Householder transformation. + var h = 0.0; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i].MagnitudeSquared(); + } + + var g = Math.Sqrt(h); + if (ort[m].Magnitude != 0) + { + h = h + (ort[m].Magnitude * g); + g /= ort[m].Magnitude; + ort[m] = (1.0 + g) * ort[m]; + } + else + { + ort[m] = g; + matrixH[m, m - 1] = scale; + } + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = Complex.Zero; + for (var i = order - 1; i >= m; i--) + { + f += ort[i].Conjugate() * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = Complex.Zero; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j].Conjugate(); + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] *= -g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = i == j ? Complex.One : Complex.Zero; + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != Complex.Zero && ort[m] != Complex.Zero) + { + var norm = (matrixH[m, m - 1].Real * ort[m].Real) + (matrixH[m, m - 1].Imaginary * ort[m].Imaginary); + + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = Complex.Zero; + for (var i = m; i < order; i++) + { + g += ort[i].Conjugate() * dataEv[(j * order) + i]; + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + dataEv[(j * order) + i] += g * ort[i]; + } + } + } + } + + // Create real subdiagonal elements. + for (var i = 1; i < order; i++) + { + if (matrixH[i, i - 1].Imaginary != 0.0) + { + var y = matrixH[i, i - 1] / matrixH[i, i - 1].Magnitude; + matrixH[i, i - 1] = matrixH[i, i - 1].Magnitude; + for (var j = i; j < order; j++) + { + matrixH[i, j] *= y.Conjugate(); + } + + for (var j = 0; j <= Math.Min(i + 1, order - 1); j++) + { + matrixH[j, i] *= y; + } + + for (var j = 0; j < order; j++) + { + dataEv[(i * order) + j] *= y; + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of the eigenvectors + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void NonsymmetricReduceHessenberToRealSchur(Complex[] vectorV, Complex[] dataEv, Complex[,] matrixH, int order) + { + // Initialize + var n = order - 1; + var eps = Precision.DoubleMachinePrecision; + + double norm; + Complex s, x, y, z, exshift = Complex.Zero; + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var tst1 = Math.Abs(matrixH[l - 1, l - 1].Real) + Math.Abs(matrixH[l - 1, l - 1].Imaginary) + Math.Abs(matrixH[l, l].Real) + Math.Abs(matrixH[l, l].Imaginary); + if (Math.Abs(matrixH[l, l - 1].Real) < eps * tst1) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] += exshift; + vectorV[n] = matrixH[n, n]; + n--; + iter = 0; + } + else + { + // Form shift + if (iter != 10 && iter != 20) + { + s = matrixH[n, n]; + x = matrixH[n - 1, n] * matrixH[n, n - 1].Real; + + if (x.Real != 0.0 || x.Imaginary != 0.0) + { + y = (matrixH[n - 1, n - 1] - s) / 2.0; + z = ((y * y) + x).SquareRoot(); + if ((y.Real * z.Real) + (y.Imaginary * z.Imaginary) < 0.0) + { + z *= -1.0; + } + + x /= y + z; + s = s - x; + } + } + else + { + // Form exceptional shift + s = Math.Abs(matrixH[n, n - 1].Real) + Math.Abs(matrixH[n - 1, n - 2].Real); + } + + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + iter++; + + // Reduce to triangle (rows) + for (var i = l + 1; i <= n; i++) + { + s = matrixH[i, i - 1].Real; + norm = SpecialFunctions.Hypotenuse(matrixH[i - 1, i - 1].Magnitude, s.Real); + x = matrixH[i - 1, i - 1] / norm; + vectorV[i - 1] = x; + matrixH[i - 1, i - 1] = norm; + matrixH[i, i - 1] = new Complex(0.0, s.Real / norm); + + for (var j = i; j < order; j++) + { + y = matrixH[i - 1, j]; + z = matrixH[i, j]; + matrixH[i - 1, j] = (x.Conjugate() * y) + (matrixH[i, i - 1].Imaginary * z); + matrixH[i, j] = (x * z) - (matrixH[i, i - 1].Imaginary * y); + } + } + + s = matrixH[n, n]; + if (s.Imaginary != 0.0) + { + s /= matrixH[n, n].Magnitude; + matrixH[n, n] = matrixH[n, n].Magnitude; + + for (var j = n + 1; j < order; j++) + { + matrixH[n, j] *= s.Conjugate(); + } + } + + // Inverse operation (columns). + for (var j = l + 1; j <= n; j++) + { + x = vectorV[j - 1]; + for (var i = 0; i <= j; i++) + { + z = matrixH[i, j]; + if (i != j) + { + y = matrixH[i, j - 1]; + matrixH[i, j - 1] = (x * y) + (matrixH[j, j - 1].Imaginary * z); + } + else + { + y = matrixH[i, j - 1].Real; + matrixH[i, j - 1] = new Complex((x.Real * y.Real) - (x.Imaginary * y.Imaginary) + (matrixH[j, j - 1].Imaginary * z.Real), matrixH[i, j - 1].Imaginary); + } + + matrixH[i, j] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + } + + for (var i = 0; i < order; i++) + { + y = dataEv[((j - 1) * order) + i]; + z = dataEv[(j * order) + i]; + dataEv[((j - 1) * order) + i] = (x * y) + (matrixH[j, j - 1].Imaginary * z); + dataEv[(j * order) + i] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + } + } + + if (s.Imaginary != 0.0) + { + for (var i = 0; i <= n; i++) + { + matrixH[i, n] *= s; + } + + for (var i = 0; i < order; i++) + { + dataEv[(n * order) + i] *= s; + } + } + } + } + + // All roots found. + // Backsubstitute to find vectors of upper triangular form + norm = 0.0; + for (var i = 0; i < order; i++) + { + for (var j = i; j < order; j++) + { + norm = Math.Max(norm, Math.Abs(matrixH[i, j].Real) + Math.Abs(matrixH[i, j].Imaginary)); + } + } + + if (order == 1) + { + return; + } + + if (norm == 0.0) + { + return; + } + + for (n = order - 1; n > 0; n--) + { + x = vectorV[n]; + matrixH[n, n] = 1.0; + + for (var i = n - 1; i >= 0; i--) + { + z = 0.0; + for (var j = i + 1; j <= n; j++) + { + z += matrixH[i, j] * matrixH[j, n]; + } + + y = x - vectorV[i]; + if (y.Real == 0.0 && y.Imaginary == 0.0) + { + y = eps * norm; + } + + matrixH[i, n] = z / y; + + // Overflow control + var tr = Math.Abs(matrixH[i, n].Real) + Math.Abs(matrixH[i, n].Imaginary); + if ((eps * tr) * tr > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / tr; + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j > 0; j--) + { + for (var i = 0; i < order; i++) + { + z = Complex.Zero; + for (var k = 0; k <= j; k++) + { + z += dataEv[(k * order) + i] * matrixH[k, j]; + } + + dataEv[(j * order) + i] = 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"); + } + + // 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 (VectorEv.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VectorEv.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = VectorEv.Count; + var tmp = new Complex[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + Complex value = 0.0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i].Conjugate() * input.At(i, k); + } + + value /= VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + Complex value = 0.0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j, k] = value; + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD 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 m matrix + // Check that b is a column vector with m entries + if (VectorEv.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VectorEv.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VH * b; + var order = VectorEv.Count; + var tmp = new Complex[order]; + Complex value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i].Conjugate() * input[i]; + } + + value /= VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// 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; + } + } +} \ No newline at end of file diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/DenseGramSchmidt.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseGramSchmidt.cs new file mode 100644 index 00000000..048b84bc --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,239 @@ +// +// 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 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 DenseGramSchmidt : GramSchmidt + { + /// + /// 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 DenseGramSchmidt(DenseMatrix 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); + Factorize(((DenseMatrix)MatrixQ).Data, MatrixQ.RowCount, MatrixQ.ColumnCount, ((DenseMatrix)MatrixR).Data); + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(Complex[] q, int rowsQ, int columnsQ, Complex[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i].Magnitude * q[(k * rowsQ) + i].Magnitude; + } + + norm = Math.Sqrt(norm); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + int k1 = k; + int j1 = j; + var dot = CommonParallel.Aggregate(0, rowsQ, index => q[(k1 * rowsQ) + index].Conjugate() * q[(j1 * rowsQ) + index]); + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// 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 dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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 (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 dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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; + } + + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs index aa06b67e..df1302ba 100644 --- a/src/Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs @@ -90,7 +90,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization if (IsSymmetric) { - var matrixCopy = matrix.Clone(); + var matrixCopy = matrix.ToArray(); var tau = new Complex[order]; var d = new double[order]; var e = new double[order]; @@ -126,14 +126,14 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. - private static void SymmetricTridiagonalize(Matrix matrixA, double[] d, double[] e, Complex[] tau, int order) + private static void SymmetricTridiagonalize(Complex[,] matrixA, double[] d, double[] e, Complex[] tau, int order) { double hh; tau[order - 1] = Complex.One; - for (var i = 0; i < matrixA.Diagonal().Count; i++) + for (var i = 0; i < order; i++) { - d[i] = matrixA.Diagonal()[i].Real; + d[i] = matrixA[i, i].Real; } // Householder reduction to tridiagonal form. @@ -331,9 +331,9 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization // Accumulate transformation. for (var k = 0; k < order; k++) { - h = MatrixEv[k, i + 1].Real; - MatrixEv[k, i + 1] = (s * MatrixEv[k, i].Real) + (c * h); - MatrixEv[k, i] = (c * MatrixEv[k, i].Real) - (s * h); + h = MatrixEv.At(k, i + 1).Real; + MatrixEv.At(k, i + 1, (s * MatrixEv.At(k, i).Real) + (c * h)); + MatrixEv.At(k, i, (c * MatrixEv.At(k, i).Real) - (s * h)); } } @@ -375,9 +375,9 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization d[i] = p; for (var j = 0; j < order; j++) { - p = MatrixEv[j, i].Real; - MatrixEv[j, i] = MatrixEv[j, k]; - MatrixEv[j, k] = p; + p = MatrixEv.At(j, i).Real; + MatrixEv.At(j, i, MatrixEv.At(j, k)); + MatrixEv.At(j, k, p); } } } @@ -393,13 +393,13 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. - private void SymmetricUntridiagonalize(Matrix matrixA, Complex[] tau, int order) + private void SymmetricUntridiagonalize(Complex[,] matrixA, Complex[] tau, int order) { for (var i = 0; i < order; i++) { for (var j = 0; j < order; j++) { - MatrixEv[i, j] = MatrixEv[i, j].Real * tau[i].Conjugate(); + MatrixEv.At(i, j, MatrixEv.At(i, j).Real * tau[i].Conjugate()); } } @@ -414,14 +414,14 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization var s = Complex.Zero; for (var k = 0; k < i; k++) { - s += MatrixEv[k, j] * matrixA[i, k]; + s += MatrixEv.At(k, j) * matrixA[i, k]; } s = (s / h) / h; for (var k = 0; k < i; k++) { - MatrixEv[k, j] -= s * matrixA[i, k].Conjugate(); + MatrixEv.At(k, j, MatrixEv.At(k, j) - s * matrixA[i, k].Conjugate()); } } } @@ -515,7 +515,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization { for (var j = 0; j < order; j++) { - MatrixEv[i, j] = i == j ? Complex.One : Complex.Zero; + MatrixEv.At(i, j, i == j ? Complex.One : Complex.Zero); } } @@ -535,14 +535,14 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization var g = Complex.Zero; for (var i = m; i < order; i++) { - g += ort[i].Conjugate() * MatrixEv[i, j]; + g += ort[i].Conjugate() * MatrixEv.At(i, j); } // Double division avoids possible underflow g /= norm; for (var i = m; i < order; i++) { - MatrixEv[i, j] += g * ort[i]; + MatrixEv.At(i, j, MatrixEv.At(i, j) + g * ort[i]); } } } @@ -567,7 +567,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization for (var j = 0; j < order; j++) { - MatrixEv[j, i] *= y; + MatrixEv.At(j, i, MatrixEv.At(j, i) * y); } } } @@ -706,10 +706,10 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization for (var i = 0; i < order; i++) { - y = MatrixEv[i, j - 1]; - z = MatrixEv[i, j]; - MatrixEv[i, j - 1] = (x * y) + (matrixH[j, j - 1].Imaginary * z); - MatrixEv[i, j] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + y = MatrixEv.At(i, j - 1); + z = MatrixEv.At(i, j); + MatrixEv.At(i, j - 1, (x * y) + (matrixH[j, j - 1].Imaginary * z)); + MatrixEv.At(i, j, (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y)); } } @@ -722,7 +722,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization for (var i = 0; i < order; i++) { - MatrixEv[i, n] *= s; + MatrixEv.At(i, n, MatrixEv.At(i, n) * s); } } } @@ -790,10 +790,10 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization z = Complex.Zero; for (var k = 0; k <= j; k++) { - z += MatrixEv[i, k] * matrixH[k, j]; + z += MatrixEv.At(i, k) * matrixH[k, j]; } - MatrixEv[i, j] = z; + MatrixEv.At(i, j, z); } } } @@ -848,7 +848,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization { for (var i = 0; i < order; i++) { - value += MatrixEv.At(i, j) * input.At(i, k); + value += MatrixEv.At(i, j).Conjugate() * input.At(i, k); } value /= VectorEv[j].Real; @@ -862,7 +862,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization Complex value = 0.0; for (var i = 0; i < order; i++) { - value += MatrixEv.At(j, i).Conjugate() * tmp[i]; + value += MatrixEv.At(j, i) * tmp[i]; } result[j, k] = value; @@ -919,7 +919,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization { for (var i = 0; i < order; i++) { - value += MatrixEv.At(i, j) * input[i]; + value += MatrixEv.At(i, j).Conjugate() * input[i]; } value /= VectorEv[j].Real; @@ -933,7 +933,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization value = 0; for (int i = 0; i < order; i++) { - value += MatrixEv.At(j, i).Conjugate() * tmp[i]; + value += MatrixEv.At(j, i) * tmp[i]; } result[j] = value; diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/UserGramSchmidt.cs similarity index 87% rename from src/Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs rename to src/Numerics/LinearAlgebra/Complex/Factorization/UserGramSchmidt.cs index 58ded73f..2ce704b4 100644 --- a/src/Numerics/LinearAlgebra/Complex/Factorization/GramSchmidt.cs +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/UserGramSchmidt.cs @@ -1,4 +1,4 @@ -// +// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics @@ -43,17 +43,17 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization /// /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. /// - public class GramSchmidt : QR + public class UserGramSchmidt : GramSchmidt { /// - /// Initializes a new instance of the class. This object creates an unitary matrix + /// 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) + public UserGramSchmidt(Matrix matrix) { if (matrix == null) { @@ -100,44 +100,6 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Factorization } } - /// - /// 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. /// diff --git a/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/MlkBiCgStab.cs b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/MlkBiCgStab.cs index 41791696..e3621bb7 100644 --- a/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/MlkBiCgStab.cs +++ b/src/Numerics/LinearAlgebra/Complex/Solvers/Iterative/MlkBiCgStab.cs @@ -36,8 +36,8 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers.Iterative using System.Linq; using System.Numerics; using Distributions; - using Factorization; using Generic; + using Generic.Factorization; using Generic.Solvers; using Generic.Solvers.Preconditioners; using Generic.Solvers.Status; @@ -642,7 +642,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex.Solvers.Iterative } // Compute the orthogonalization. - var gs = new GramSchmidt(matrix); + var gs = matrix.GramSchmidt(); var orthogonalMatrix = gs.Q; // Now transfer this to vectors diff --git a/src/Numerics/LinearAlgebra/Complex/SparseVector.cs b/src/Numerics/LinearAlgebra/Complex/SparseVector.cs index c0573acc..5d2673a0 100644 --- a/src/Numerics/LinearAlgebra/Complex/SparseVector.cs +++ b/src/Numerics/LinearAlgebra/Complex/SparseVector.cs @@ -28,6 +28,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex { using System; using System.Collections.Generic; + using System.Linq; using System.Numerics; using Distributions; using Generic; @@ -1238,6 +1239,11 @@ namespace MathNet.Numerics.LinearAlgebra.Complex return 0.0; } + if (2.0 == p) + { + return _nonZeroValues.Aggregate(Complex.Zero, SpecialFunctions.Hypotenuse).Magnitude; + } + if (Double.IsPositiveInfinity(p)) { return CommonParallel.Select(0, NonZerosCount, (index, localData) => Math.Max(localData, _nonZeroValues[index].Magnitude), Math.Max); diff --git a/src/Numerics/LinearAlgebra/Complex32/DenseVector.cs b/src/Numerics/LinearAlgebra/Complex32/DenseVector.cs index 21266793..f6ffb256 100644 --- a/src/Numerics/LinearAlgebra/Complex32/DenseVector.cs +++ b/src/Numerics/LinearAlgebra/Complex32/DenseVector.cs @@ -28,6 +28,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32 { using System; using System.Collections.Generic; + using System.Linq; using Distributions; using Generic; using NumberTheory; @@ -1185,6 +1186,11 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32 index => Data[index].Magnitude); } + if (2.0 == p) + { + return Data.Aggregate(Complex32.Zero, SpecialFunctions.Hypotenuse).Magnitude; + } + if (Double.IsPositiveInfinity(p)) { return CommonParallel.Select( diff --git a/src/Numerics/LinearAlgebra/Complex32/Factorization/DenseEvd.cs b/src/Numerics/LinearAlgebra/Complex32/Factorization/DenseEvd.cs new file mode 100644 index 00000000..dc40cac2 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex32/Factorization/DenseEvd.cs @@ -0,0 +1,968 @@ +// +// 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.Complex32.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Numerics; + using Properties; + + /// + /// Eigenvalues and eigenvectors of a complex matrix. + /// + /// + /// If A is hermitan, then A = V*D*V' where the eigenvalue matrix D is + /// diagonal and the eigenvector matrix V is hermitan. + /// I.e. A = V*D*V' and V*VH=I. + /// If A is not symmetric, then the eigenvalue matrix D is block diagonal + /// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, + /// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The + /// columns of V represent the eigenvectors in the sense that A*V = V*D, + /// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly + /// conditioned, or even singular, so the validity of the equation + /// A = V*D*Inverse(V) depends upon V.cond(). + /// + public class DenseEvd : Evd + { + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public DenseEvd(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matricies for eigenvalues and eigenvectors + MatrixEv = DenseMatrix.Identity(order); + MatrixD = matrix.CreateMatrix(order, order); + VectorEv = new LinearAlgebra.Complex.DenseVector(order); + + IsSymmetric = true; + + for (var i = 0; i < order & IsSymmetric; i++) + { + for (var j = 0; j < order & IsSymmetric; j++) + { + IsSymmetric &= matrix[i, j] == matrix[j, i].Conjugate(); + } + } + + if (IsSymmetric) + { + var matrixCopy = matrix.ToArray(); + var tau = new Complex32[order]; + var d = new float[order]; + var e = new float[order]; + + SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + SymmetricDiagonalize(((DenseMatrix)MatrixEv).Data, d, e, order); + SymmetricUntridiagonalize(((DenseMatrix)MatrixEv).Data, matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + VectorEv[i] = new Complex(d[i], e[i]); + } + } + else + { + var matrixH = matrix.ToArray(); + NonsymmetricReduceToHessenberg(((DenseMatrix)MatrixEv).Data, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(((LinearAlgebra.Complex.DenseVector)VectorEv).Data, ((DenseMatrix)MatrixEv).Data, matrixH, order); + } + + for (var i = 0; i < VectorEv.Count; i++) + { + MatrixD.At(i, i, (Complex32)VectorEv[i]); + } + } + + /// + /// Reduces a complex hermitian matrix to a real symmetric tridiagonal matrix using unitary similarity transformations. + /// + /// Source matrix to reduce + /// Output: Arrays for internal storage of real parts of eigenvalues + /// Output: Arrays for internal storage of imaginary parts of eigenvalues + /// Output: Arrays that contains further information about the transformations. + /// Order of initial matrix + /// This is derived from the Algol procedures HTRIDI by + /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricTridiagonalize(Complex32[,] matrixA, float[] d, float[] e, Complex32[] tau, int order) + { + float hh; + tau[order - 1] = Complex32.One; + + for (var i = 0; i < order; i++) + { + d[i] = matrixA[i, i].Real; + } + + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0f; + var h = 0.0f; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(matrixA[i, k].Real) + Math.Abs(matrixA[i, k].Imaginary); + } + + if (scale == 0.0f) + { + tau[i - 1] = Complex32.One; + e[i] = 0.0f; + } + else + { + for (var k = 0; k < i; k++) + { + matrixA[i, k] /= scale; + h += matrixA[i, k].MagnitudeSquared; + } + + Complex32 g = (float)Math.Sqrt(h); + e[i] = scale * g.Real; + + Complex32 temp; + var f = matrixA[i, i - 1]; + if (f.Magnitude != 0) + { + temp = -(matrixA[i, i - 1].Conjugate() * tau[i].Conjugate()) / f.Magnitude; + h += f.Magnitude * g.Real; + g = 1.0f + (g / f.Magnitude); + matrixA[i, i - 1] *= g; + } + else + { + temp = -tau[i].Conjugate(); + matrixA[i, i - 1] = g; + } + + if ((f.Magnitude == 0) || (i != 1)) + { + f = Complex32.Zero; + for (var j = 0; j < i; j++) + { + var tmp = Complex32.Zero; + + // Form element of A*U. + for (var k = 0; k <= j; k++) + { + tmp += matrixA[j, k] * matrixA[i, k].Conjugate(); + } + + for (var k = j + 1; k <= i - 1; k++) + { + tmp += matrixA[k, j].Conjugate() * matrixA[i, k].Conjugate(); + } + + // Form element of P + tau[j] = tmp / h; + f += (tmp / h) * matrixA[i, j]; + } + + hh = f.Real / (h + h); + + // Form the reduced A. + for (var j = 0; j < i; j++) + { + f = matrixA[i, j].Conjugate(); + g = tau[j] - (hh * f); + tau[j] = g.Conjugate(); + + for (var k = 0; k <= j; k++) + { + matrixA[j, k] -= (f * tau[k]) + (g * matrixA[i, k]); + } + } + } + + for (var k = 0; k < i; k++) + { + matrixA[i, k] *= scale; + } + + tau[i - 1] = temp.Conjugate(); + } + + hh = d[i]; + d[i] = matrixA[i, i].Real; + matrixA[i, i] = new Complex32(hh, scale * (float)Math.Sqrt(h)); + } + + hh = d[0]; + d[0] = matrixA[0, 0].Real; + matrixA[0, 0] = hh; + e[0] = 0.0f; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricDiagonalize(Complex32[] dataEv, float[] d, float[] e, int order) + { + const int Maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0f; + + var f = 0.0f; + var tst1 = 0.0f; + var eps = Precision.DoubleMachinePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0f * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0f); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0f; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0f; + var s2 = 0.0f; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = dataEv[((i + 1) * order) + k].Real; + dataEv[((i + 1) * order) + k] = (s * dataEv[(i * order) + k].Real) + (c * h); + dataEv[(i * order) + k] = (c * dataEv[(i * order) + k].Real) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= Maxiter) + { + throw new ArgumentException(Resources.ConvergenceFailed); + } + } + while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0f; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = dataEv[(i * order) + j].Real; + dataEv[(i * order) + j] = dataEv[(k * order) + j]; + dataEv[(k * order) + j] = p; + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// Data array of matrix V (eigenvectors) + /// Previously tridiagonalized matrix by . + /// Contains further information about the transformations + /// Input matrix order + /// This is derived from the Algol procedures HTRIBK, by + /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricUntridiagonalize(Complex32[] dataEv, Complex32[,] matrixA, Complex32[] tau, int order) + { + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = dataEv[(j * order) + i].Real * tau[i].Conjugate(); + } + } + + // Recover and apply the Householder matrices. + for (var i = 1; i < order; i++) + { + var h = matrixA[i, i].Imaginary; + if (h != 0) + { + for (var j = 0; j < order; j++) + { + var s = Complex32.Zero; + for (var k = 0; k < i; k++) + { + s += dataEv[(j * order) + k] * matrixA[i, k]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + dataEv[(j * order) + k] -= s * matrixA[i, k].Conjugate(); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + private static void NonsymmetricReduceToHessenberg(Complex32[] dataEv, Complex32[,] matrixH, int order) + { + var ort = new Complex32[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0f; + for (var i = m; i < order; i++) + { + scale += Math.Abs(matrixH[i, m - 1].Real) + Math.Abs(matrixH[i, m - 1].Imaginary); + } + + if (scale != 0.0f) + { + // Compute Householder transformation. + var h = 0.0f; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i].MagnitudeSquared; + } + + var g = (float)Math.Sqrt(h); + if (ort[m].Magnitude != 0) + { + h = h + (ort[m].Magnitude * g); + g /= ort[m].Magnitude; + ort[m] = (1.0f + g) * ort[m]; + } + else + { + ort[m] = g; + matrixH[m, m - 1] = scale; + } + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = Complex32.Zero; + for (var i = order - 1; i >= m; i--) + { + f += ort[i].Conjugate() * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = Complex32.Zero; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j].Conjugate(); + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] *= -g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + dataEv[(j * order) + i] = i == j ? Complex32.One : Complex32.Zero; + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != Complex32.Zero && ort[m] != Complex32.Zero) + { + var norm = (matrixH[m, m - 1].Real * ort[m].Real) + (matrixH[m, m - 1].Imaginary * ort[m].Imaginary); + + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = Complex32.Zero; + for (var i = m; i < order; i++) + { + g += ort[i].Conjugate() * dataEv[(j * order) + i]; + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + dataEv[(j * order) + i] += g * ort[i]; + } + } + } + } + + // Create real subdiagonal elements. + for (var i = 1; i < order; i++) + { + if (matrixH[i, i - 1].Imaginary != 0.0f) + { + var y = matrixH[i, i - 1] / matrixH[i, i - 1].Magnitude; + matrixH[i, i - 1] = matrixH[i, i - 1].Magnitude; + for (var j = i; j < order; j++) + { + matrixH[i, j] *= y.Conjugate(); + } + + for (var j = 0; j <= Math.Min(i + 1, order - 1); j++) + { + matrixH[j, i] *= y; + } + + for (var j = 0; j < order; j++) + { + dataEv[(i * order) + j] *= y; + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of the eigenvectors + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void NonsymmetricReduceHessenberToRealSchur(Complex[] vectorV, Complex32[] dataEv, Complex32[,] matrixH, int order) + { + // Initialize + var n = order - 1; + var eps = (float)Precision.SingleMachinePrecision; + + float norm; + Complex32 s, x, y, z, exshift = Complex32.Zero; + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + var tst1 = Math.Abs(matrixH[l - 1, l - 1].Real) + Math.Abs(matrixH[l - 1, l - 1].Imaginary) + Math.Abs(matrixH[l, l].Real) + Math.Abs(matrixH[l, l].Imaginary); + if (Math.Abs(matrixH[l, l - 1].Real) < eps * tst1) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] += exshift; + vectorV[n] = matrixH[n, n].ToComplex(); + n--; + iter = 0; + } + else + { + // Form shift + if (iter != 10 && iter != 20) + { + s = matrixH[n, n]; + x = matrixH[n - 1, n] * matrixH[n, n - 1].Real; + + if (x.Real != 0.0f || x.Imaginary != 0.0f) + { + y = (matrixH[n - 1, n - 1] - s) / 2.0f; + z = ((y * y) + x).SquareRoot(); + if ((y.Real * z.Real) + (y.Imaginary * z.Imaginary) < 0.0f) + { + z *= -1.0f; + } + + x /= y + z; + s = s - x; + } + } + else + { + // Form exceptional shift + s = Math.Abs(matrixH[n, n - 1].Real) + Math.Abs(matrixH[n - 1, n - 2].Real); + } + + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + iter++; + + // Reduce to triangle (rows) + for (var i = l + 1; i <= n; i++) + { + s = matrixH[i, i - 1].Real; + norm = SpecialFunctions.Hypotenuse(matrixH[i - 1, i - 1].Magnitude, s.Real); + x = matrixH[i - 1, i - 1] / norm; + vectorV[i - 1] = x.ToComplex(); + matrixH[i - 1, i - 1] = norm; + matrixH[i, i - 1] = new Complex32(0.0f, s.Real / norm); + + for (var j = i; j < order; j++) + { + y = matrixH[i - 1, j]; + z = matrixH[i, j]; + matrixH[i - 1, j] = (x.Conjugate() * y) + (matrixH[i, i - 1].Imaginary * z); + matrixH[i, j] = (x * z) - (matrixH[i, i - 1].Imaginary * y); + } + } + + s = matrixH[n, n]; + if (s.Imaginary != 0.0f) + { + s /= matrixH[n, n].Magnitude; + matrixH[n, n] = matrixH[n, n].Magnitude; + + for (var j = n + 1; j < order; j++) + { + matrixH[n, j] *= s.Conjugate(); + } + } + + // Inverse operation (columns). + for (var j = l + 1; j <= n; j++) + { + x = (Complex32)vectorV[j - 1]; + for (var i = 0; i <= j; i++) + { + z = matrixH[i, j]; + if (i != j) + { + y = matrixH[i, j - 1]; + matrixH[i, j - 1] = (x * y) + (matrixH[j, j - 1].Imaginary * z); + } + else + { + y = matrixH[i, j - 1].Real; + matrixH[i, j - 1] = new Complex32((x.Real * y.Real) - (x.Imaginary * y.Imaginary) + (matrixH[j, j - 1].Imaginary * z.Real), matrixH[i, j - 1].Imaginary); + } + + matrixH[i, j] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + } + + for (var i = 0; i < order; i++) + { + y = dataEv[((j - 1) * order) + i]; + z = dataEv[(j * order) + i]; + dataEv[((j - 1) * order) + i] = (x * y) + (matrixH[j, j - 1].Imaginary * z); + dataEv[(j * order) + i] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + } + } + + if (s.Imaginary != 0.0f) + { + for (var i = 0; i <= n; i++) + { + matrixH[i, n] *= s; + } + + for (var i = 0; i < order; i++) + { + dataEv[(n * order) + i] *= s; + } + } + } + } + + // All roots found. + // Backsubstitute to find vectors of upper triangular form + norm = 0.0f; + for (var i = 0; i < order; i++) + { + for (var j = i; j < order; j++) + { + norm = Math.Max(norm, Math.Abs(matrixH[i, j].Real) + Math.Abs(matrixH[i, j].Imaginary)); + } + } + + if (order == 1) + { + return; + } + + if (norm == 0.0f) + { + return; + } + + for (n = order - 1; n > 0; n--) + { + x = (Complex32)vectorV[n]; + matrixH[n, n] = 1.0f; + + for (var i = n - 1; i >= 0; i--) + { + z = 0.0f; + for (var j = i + 1; j <= n; j++) + { + z += matrixH[i, j] * matrixH[j, n]; + } + + y = x - (Complex32)vectorV[i]; + if (y.Real == 0.0f && y.Imaginary == 0.0f) + { + y = eps * norm; + } + + matrixH[i, n] = z / y; + + // Overflow control + var tr = Math.Abs(matrixH[i, n].Real) + Math.Abs(matrixH[i, n].Imaginary); + if ((eps * tr) * tr > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / tr; + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j > 0; j--) + { + for (var i = 0; i < order; i++) + { + z = Complex32.Zero; + for (var k = 0; k <= j; k++) + { + z += dataEv[(k * order) + i] * matrixH[k, j]; + } + + dataEv[(j * order) + i] = 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"); + } + + // 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 (VectorEv.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VectorEv.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = VectorEv.Count; + var tmp = new Complex32[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + Complex32 value = 0.0f; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i].Conjugate() * input.At(i, k); + } + + value /= (float)VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + Complex32 value = 0.0f; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j, k] = value; + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD 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 m matrix + // Check that b is a column vector with m entries + if (VectorEv.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VectorEv.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VH * b; + var order = VectorEv.Count; + var tmp = new Complex32[order]; + Complex32 value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i].Conjugate() * input[i]; + } + + value /= (float)VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override Complex32 MultiplyT(Complex32 val1, Complex32 val2) + { + return val1 * val2; + } + } +} \ No newline at end of file diff --git a/src/Numerics/LinearAlgebra/Complex32/Factorization/DenseGramSchmidt.cs b/src/Numerics/LinearAlgebra/Complex32/Factorization/DenseGramSchmidt.cs new file mode 100644 index 00000000..42d21502 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex32/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,239 @@ +// +// 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.Complex32.Factorization +{ + using System; + using Generic; + using Generic.Factorization; + using Numerics; + using Properties; + using Threading; + + /// + /// 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 DenseGramSchmidt : GramSchmidt + { + /// + /// 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 DenseGramSchmidt(DenseMatrix 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); + Factorize(((DenseMatrix)MatrixQ).Data, MatrixQ.RowCount, MatrixQ.ColumnCount, ((DenseMatrix)MatrixR).Data); + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(Complex32[] q, int rowsQ, int columnsQ, Complex32[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0f; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i].Magnitude * q[(k * rowsQ) + i].Magnitude; + } + + norm = (float)Math.Sqrt(norm); + if (norm == 0.0f) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + int k1 = k; + int j1 = j; + var dot = CommonParallel.Aggregate(0, rowsQ, index => q[(k1 * rowsQ) + index].Conjugate() * q[(j1 * rowsQ) + index]); + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// 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 dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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 (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 dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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 Complex32 MultiplyT(Complex32 val1, Complex32 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(Complex32 val1) + { + return val1.Magnitude; + } + + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs index 57d56040..a86371b9 100644 --- a/src/Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs +++ b/src/Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs @@ -91,7 +91,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization if (IsSymmetric) { - var matrixCopy = matrix.Clone(); + var matrixCopy = matrix.ToArray(); var tau = new Complex32[order]; var d = new float[order]; var e = new float[order]; @@ -114,7 +114,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization for (var i = 0; i < VectorEv.Count; i++) { - MatrixD[i, i] = (Complex32)VectorEv[i]; + MatrixD.At(i, i, (Complex32)VectorEv[i]); } } @@ -130,14 +130,14 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. - private static void SymmetricTridiagonalize(Matrix matrixA, float[] d, float[] e, Complex32[] tau, int order) + private static void SymmetricTridiagonalize(Complex32[,] matrixA, float[] d, float[] e, Complex32[] tau, int order) { float hh; tau[order - 1] = Complex32.One; - for (var i = 0; i < matrixA.Diagonal().Count; i++) + for (var i = 0; i < order; i++) { - d[i] = matrixA.Diagonal()[i].Real; + d[i] = matrixA[i, i].Real; } // Householder reduction to tridiagonal form. @@ -335,9 +335,9 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization // Accumulate transformation. for (var k = 0; k < order; k++) { - h = MatrixEv[k, i + 1].Real; - MatrixEv[k, i + 1] = (s * MatrixEv[k, i].Real) + (c * h); - MatrixEv[k, i] = (c * MatrixEv[k, i].Real) - (s * h); + h = MatrixEv.At(k, i + 1).Real; + MatrixEv.At(k, i + 1, (s * MatrixEv.At(k, i).Real) + (c * h)); + MatrixEv.At(k, i, (c * MatrixEv.At(k, i).Real) - (s * h)); } } @@ -379,9 +379,9 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization d[i] = p; for (var j = 0; j < order; j++) { - p = MatrixEv[j, i].Real; - MatrixEv[j, i] = MatrixEv[j, k]; - MatrixEv[j, k] = p; + p = MatrixEv.At(j, i).Real; + MatrixEv.At(j, i, MatrixEv.At(j, k)); + MatrixEv.At(j, k, p); } } } @@ -397,13 +397,13 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. - private void SymmetricUntridiagonalize(Matrix matrixA, Complex32[] tau, int order) + private void SymmetricUntridiagonalize(Complex32[,] matrixA, Complex32[] tau, int order) { for (var i = 0; i < order; i++) { for (var j = 0; j < order; j++) { - MatrixEv[i, j] = MatrixEv[i, j].Real * tau[i].Conjugate(); + MatrixEv.At(i, j, MatrixEv.At(i, j).Real * tau[i].Conjugate()); } } @@ -418,14 +418,14 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization var s = Complex32.Zero; for (var k = 0; k < i; k++) { - s += MatrixEv[k, j] * matrixA[i, k]; + s += MatrixEv.At(k, j) * matrixA[i, k]; } s = (s / h) / h; for (var k = 0; k < i; k++) { - MatrixEv[k, j] -= s * matrixA[i, k].Conjugate(); + MatrixEv.At(k, j, MatrixEv.At(k, j) - s * matrixA[i, k].Conjugate()); } } } @@ -519,7 +519,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization { for (var j = 0; j < order; j++) { - MatrixEv[i, j] = i == j ? Complex32.One : Complex32.Zero; + MatrixEv.At(i, j, i == j ? Complex32.One : Complex32.Zero); } } @@ -539,14 +539,14 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization var g = Complex32.Zero; for (var i = m; i < order; i++) { - g += ort[i].Conjugate() * MatrixEv[i, j]; + g += ort[i].Conjugate() * MatrixEv.At(i, j); } // Double division avoids possible underflow g /= norm; for (var i = m; i < order; i++) { - MatrixEv[i, j] += g * ort[i]; + MatrixEv.At(i, j, MatrixEv.At(i, j) + g * ort[i]); } } } @@ -571,7 +571,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization for (var j = 0; j < order; j++) { - MatrixEv[j, i] *= y; + MatrixEv.At(j, i, MatrixEv.At(j, i) * y); } } } @@ -710,10 +710,10 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization for (var i = 0; i < order; i++) { - y = MatrixEv[i, j - 1]; - z = MatrixEv[i, j]; - MatrixEv[i, j - 1] = (x * y) + (matrixH[j, j - 1].Imaginary * z); - MatrixEv[i, j] = (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y); + y = MatrixEv.At(i, j - 1); + z = MatrixEv.At(i, j); + MatrixEv.At(i, j - 1, (x * y) + (matrixH[j, j - 1].Imaginary * z)); + MatrixEv.At(i, j, (x.Conjugate() * z) - (matrixH[j, j - 1].Imaginary * y)); } } @@ -726,7 +726,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization for (var i = 0; i < order; i++) { - MatrixEv[i, n] *= s; + MatrixEv.At(i, n, MatrixEv.At(i, n) * s); } } } @@ -794,10 +794,10 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization z = Complex32.Zero; for (var k = 0; k <= j; k++) { - z += MatrixEv[i, k] * matrixH[k, j]; + z += MatrixEv.At(i, k) * matrixH[k, j]; } - MatrixEv[i, j] = z; + MatrixEv.At(i, j, z); } } } @@ -852,7 +852,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization { for (var i = 0; i < order; i++) { - value += MatrixEv.At(i, j) * input.At(i, k); + value += MatrixEv.At(i, j).Conjugate() * input.At(i, k); } value /= (float)VectorEv[j].Real; @@ -866,7 +866,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization Complex32 value = 0.0f; for (var i = 0; i < order; i++) { - value += MatrixEv.At(j, i).Conjugate() * tmp[i]; + value += MatrixEv.At(j, i) * tmp[i]; } result[j, k] = value; @@ -923,7 +923,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization { for (var i = 0; i < order; i++) { - value += MatrixEv.At(i, j) * input[i]; + value += MatrixEv.At(i, j).Conjugate() * input[i]; } value /= (float)VectorEv[j].Real; @@ -937,7 +937,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization value = 0; for (int i = 0; i < order; i++) { - value += MatrixEv.At(j, i).Conjugate() * tmp[i]; + value += MatrixEv.At(j, i) * tmp[i]; } result[j] = value; diff --git a/src/Numerics/LinearAlgebra/Complex32/Factorization/GramSchmidt.cs b/src/Numerics/LinearAlgebra/Complex32/Factorization/UserGramSchmidt.cs similarity index 87% rename from src/Numerics/LinearAlgebra/Complex32/Factorization/GramSchmidt.cs rename to src/Numerics/LinearAlgebra/Complex32/Factorization/UserGramSchmidt.cs index 3ae4ae9a..385e35e9 100644 --- a/src/Numerics/LinearAlgebra/Complex32/Factorization/GramSchmidt.cs +++ b/src/Numerics/LinearAlgebra/Complex32/Factorization/UserGramSchmidt.cs @@ -1,4 +1,4 @@ -// +// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics @@ -43,17 +43,17 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization /// /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. /// - public class GramSchmidt : QR + public class UserGramSchmidt : GramSchmidt { /// - /// Initializes a new instance of the class. This object creates an unitary matrix + /// 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) + public UserGramSchmidt(Matrix matrix) { if (matrix == null) { @@ -99,45 +99,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Factorization } } } - - /// - /// 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 = Complex32.One; - for (var i = 0; i < MatrixR.ColumnCount; i++) - { - det *= MatrixR.At(i, i); - if (MatrixR.At(i, i).Magnitude.AlmostEqualInDecimalPlaces(0.0f, 7)) - { - return 0; - } - } - - return det.Magnitude; - } - } - + /// /// Solves a system of linear equations, AX = B, with A QR factorized. /// diff --git a/src/Numerics/LinearAlgebra/Complex32/Solvers/Iterative/MlkBiCgStab.cs b/src/Numerics/LinearAlgebra/Complex32/Solvers/Iterative/MlkBiCgStab.cs index c95633b7..f6e671a6 100644 --- a/src/Numerics/LinearAlgebra/Complex32/Solvers/Iterative/MlkBiCgStab.cs +++ b/src/Numerics/LinearAlgebra/Complex32/Solvers/Iterative/MlkBiCgStab.cs @@ -35,8 +35,8 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers.Iterative using System.Diagnostics; using System.Linq; using Distributions; - using Factorization; using Generic; + using Generic.Factorization; using Generic.Solvers; using Generic.Solvers.Preconditioners; using Generic.Solvers.Status; @@ -642,7 +642,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32.Solvers.Iterative } // Compute the orthogonalization. - var gs = new GramSchmidt(matrix); + var gs = matrix.GramSchmidt(); var orthogonalMatrix = gs.Q; // Now transfer this to vectors diff --git a/src/Numerics/LinearAlgebra/Complex32/SparseVector.cs b/src/Numerics/LinearAlgebra/Complex32/SparseVector.cs index af76de33..ead46d83 100644 --- a/src/Numerics/LinearAlgebra/Complex32/SparseVector.cs +++ b/src/Numerics/LinearAlgebra/Complex32/SparseVector.cs @@ -28,6 +28,7 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32 { using System; using System.Collections.Generic; + using System.Linq; using Distributions; using Generic; using NumberTheory; @@ -1238,6 +1239,11 @@ namespace MathNet.Numerics.LinearAlgebra.Complex32 return 0.0; } + if (2.0 == p) + { + return _nonZeroValues.Aggregate(Complex32.Zero, SpecialFunctions.Hypotenuse).Magnitude; + } + if (Double.IsPositiveInfinity(p)) { return CommonParallel.Select(0, NonZerosCount, (index, localData) => Math.Max(localData, _nonZeroValues[index].Magnitude), Math.Max); diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/DenseEvd.cs b/src/Numerics/LinearAlgebra/Double/Factorization/DenseEvd.cs new file mode 100644 index 00000000..5ec5ea22 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/DenseEvd.cs @@ -0,0 +1,1237 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Properties; + + /// + /// Eigenvalues and eigenvectors of a real matrix. + /// + /// + /// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is + /// diagonal and the eigenvector matrix V is orthogonal. + /// I.e. A = V*D*V' and V*VT=I. + /// If A is not symmetric, then the eigenvalue matrix D is block diagonal + /// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, + /// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The + /// columns of V represent the eigenvectors in the sense that A*V = V*D, + /// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly + /// conditioned, or even singular, so the validity of the equation + /// A = V*D*Inverse(V) depends upon V.cond(). + /// + public class DenseEvd : Evd + { + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public DenseEvd(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matricies for eigenvalues and eigenvectors + MatrixEv = matrix.CreateMatrix(order, order); + MatrixD = matrix.CreateMatrix(order, order); + VectorEv = new LinearAlgebra.Complex.DenseVector(order); + + IsSymmetric = true; + + for (var i = 0; i < order & IsSymmetric; i++) + { + for (var j = 0; j < order & IsSymmetric; j++) + { + IsSymmetric &= matrix[i, j] == matrix[j, i]; + } + } + + var d = new double[order]; + var e = new double[order]; + + if (IsSymmetric) + { + matrix.CopyTo(MatrixEv); + d = MatrixEv.Row(order - 1).ToArray(); + + SymmetricTridiagonalize(((DenseMatrix)MatrixEv).Data, d, e, order); + SymmetricDiagonalize(((DenseMatrix)MatrixEv).Data, d, e, order); + } + else + { + var matrixH = matrix.ToArray(); + + NonsymmetricReduceToHessenberg(((DenseMatrix)MatrixEv).Data, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(((DenseMatrix)MatrixEv).Data, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + MatrixD[i, i] = d[i]; + + if (e[i] > 0) + { + MatrixD.At(i, i + 1, e[i]); + } + else if (e[i] < 0) + { + MatrixD.At(i, i - 1, e[i]); + } + } + + for (var i = 0; i < order; i++) + { + VectorEv[i] = new Complex(d[i], e[i]); + } + } + + /// + /// Symmetric Householder reduction to tridiagonal form. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tred2 by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricTridiagonalize(double[] a, double[] d, double[] e, int order) + { + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0; + var h = 0.0; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(d[k]); + } + + if (scale == 0.0) + { + e[i] = d[i - 1]; + for (var j = 0; j < i; j++) + { + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0; + a[(i * order) + j] = 0.0; + } + } + else + { + // Generate Householder vector. + for (var k = 0; k < i; k++) + { + d[k] /= scale; + h += d[k] * d[k]; + } + + var f = d[i - 1]; + var g = Math.Sqrt(h); + if (f > 0) + { + g = -g; + } + + e[i] = scale * g; + h = h - (f * g); + d[i - 1] = f - g; + + for (var j = 0; j < i; j++) + { + e[j] = 0.0; + } + + // Apply similarity transformation to remaining columns. + for (var j = 0; j < i; j++) + { + f = d[j]; + a[(i * order) + j] = f; + g = e[j] + (a[(j * order) + j] * f); + + for (var k = j + 1; k <= i - 1; k++) + { + g += a[(j * order) + k] * d[k]; + e[k] += a[(j * order) + k] * f; + } + + e[j] = g; + } + + f = 0.0; + + for (var j = 0; j < i; j++) + { + e[j] /= h; + f += e[j] * d[j]; + } + + var hh = f / (h + h); + + for (var j = 0; j < i; j++) + { + e[j] -= hh * d[j]; + } + + for (var j = 0; j < i; j++) + { + f = d[j]; + g = e[j]; + + for (var k = j; k <= i - 1; k++) + { + a[(j * order) + k] -= (f * e[k]) + (g * d[k]); + } + + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0; + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + a[(i * order) + order - 1] = a[(i * order) + i]; + a[(i * order) + i] = 1.0; + var h = d[i + 1]; + if (h != 0.0) + { + for (var k = 0; k <= i; k++) + { + d[k] = a[((i + 1) * order) + k] / h; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0; + for (var k = 0; k <= i; k++) + { + g += a[((i + 1) * order) + k] * a[(j * order) + k]; + } + + for (var k = 0; k <= i; k++) + { + a[(j * order) + k] -= g * d[k]; + } + } + } + + for (var k = 0; k <= i; k++) + { + a[((i + 1) * order) + k] = 0.0; + } + } + + for (var j = 0; j < order; j++) + { + d[j] = a[(j * order) + order - 1]; + a[(j * order) + order - 1] = 0.0; + } + + a[(order * order) - 1] = 1.0; + e[0] = 0.0; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricDiagonalize(double[] a, double[] d, double[] e, int order) + { + const int Maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0; + + var f = 0.0; + var tst1 = 0.0; + var eps = Precision.DoubleMachinePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0 * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0; + var s2 = 0.0; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = a[((i + 1) * order) + k]; + a[((i + 1) * order) + k] = (s * a[(i * order) + k]) + (c * h); + a[(i * order) + k] = (c * a[(i * order) + k]) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= Maxiter) + { + throw new ArgumentException(Resources.ConvergenceFailed); + } + } + while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = a[(i * order) + j]; + a[(i * order) + j] = a[(k * order) + j]; + a[(k * order) + j] = p; + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + private static void NonsymmetricReduceToHessenberg(double[] a, double[,] matrixH, int order) + { + var ort = new double[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0; + for (var i = m; i < order; i++) + { + scale = scale + Math.Abs(matrixH[i, m - 1]); + } + + if (scale != 0.0) + { + // Compute Householder transformation. + var h = 0.0; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i] * ort[i]; + } + + var g = Math.Sqrt(h); + if (ort[m] > 0) + { + g = -g; + } + + h = h - (ort[m] * g); + ort[m] = ort[m] - g; + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = 0.0; + for (var i = order - 1; i >= m; i--) + { + f += ort[i] * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = 0.0; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j]; + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] = scale * g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + a[(j * order) + i] = i == j ? 1.0 : 0.0; + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != 0.0) + { + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = 0.0; + for (var i = m; i < order; i++) + { + g += ort[i] * a[(j * order) + i]; + } + + // Double division avoids possible underflow + g = (g / ort[m]) / matrixH[m, m - 1]; + for (var i = m; i < order; i++) + { + a[(j * order) + i] += g * ort[i]; + } + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void NonsymmetricReduceHessenberToRealSchur(double[] a, double[,] matrixH, double[] d, double[] e, int order) + { + // Initialize + var n = order - 1; + var eps = Precision.DoubleMachinePrecision; + var exshift = 0.0; + double p = 0, q = 0, r = 0, s = 0, z = 0, w, x, y; + + // Store roots isolated by balanc and compute matrix norm + var norm = 0.0; + for (var i = 0; i < order; i++) + { + for (var j = Math.Max(i - 1, 0); j < order; j++) + { + norm = norm + Math.Abs(matrixH[i, j]); + } + } + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + s = Math.Abs(matrixH[l - 1, l - 1]) + Math.Abs(matrixH[l, l]); + + if (s == 0.0) + { + s = norm; + } + + if (Math.Abs(matrixH[l, l - 1]) < eps * s) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] = matrixH[n, n] + exshift; + d[n] = matrixH[n, n]; + e[n] = 0.0; + n--; + iter = 0; + + // Two roots found + } + else if (l == n - 1) + { + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + p = (matrixH[n - 1, n - 1] - matrixH[n, n]) / 2.0; + q = (p * p) + w; + z = Math.Sqrt(Math.Abs(q)); + matrixH[n, n] = matrixH[n, n] + exshift; + matrixH[n - 1, n - 1] = matrixH[n - 1, n - 1] + exshift; + x = matrixH[n, n]; + + // Real pair + if (q >= 0) + { + if (p >= 0) + { + z = p + z; + } + else + { + z = p - z; + } + + d[n - 1] = x + z; + + d[n] = d[n - 1]; + if (z != 0.0) + { + d[n] = x - (w / z); + } + + e[n - 1] = 0.0; + e[n] = 0.0; + x = matrixH[n, n - 1]; + s = Math.Abs(x) + Math.Abs(z); + p = x / s; + q = z / s; + r = Math.Sqrt((p * p) + (q * q)); + p = p / r; + q = q / r; + + // Row modification + for (var j = n - 1; j < order; j++) + { + z = matrixH[n - 1, j]; + matrixH[n - 1, j] = (q * z) + (p * matrixH[n, j]); + matrixH[n, j] = (q * matrixH[n, j]) - (p * z); + } + + // Column modification + for (var i = 0; i <= n; i++) + { + z = matrixH[i, n - 1]; + matrixH[i, n - 1] = (q * z) + (p * matrixH[i, n]); + matrixH[i, n] = (q * matrixH[i, n]) - (p * z); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + z = a[((n - 1) * order) + i]; + a[((n - 1) * order) + i] = (q * z) + (p * a[(n * order) + i]); + a[(n * order) + i] = (q * a[(n * order) + i]) - (p * z); + } + + // Complex pair + } + else + { + d[n - 1] = x + p; + d[n] = x + p; + e[n - 1] = z; + e[n] = -z; + } + + n = n - 2; + iter = 0; + + // No convergence yet + } + else + { + // Form shift + x = matrixH[n, n]; + y = 0.0; + w = 0.0; + if (l < n) + { + y = matrixH[n - 1, n - 1]; + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + } + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + exshift += x; + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= x; + } + + s = Math.Abs(matrixH[n, n - 1]) + Math.Abs(matrixH[n - 1, n - 2]); + x = y = 0.75 * s; + w = (-0.4375) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + s = (y - x) / 2.0; + s = (s * s) + w; + if (s > 0) + { + s = Math.Sqrt(s); + if (y < x) + { + s = -s; + } + + s = x - (w / (((y - x) / 2.0) + s)); + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + x = y = w = 0.964; + } + } + + iter = iter + 1; // (Could check iteration count here.) + + // Look for two consecutive small sub-diagonal elements + var m = n - 2; + while (m >= l) + { + z = matrixH[m, m]; + r = x - z; + s = y - z; + p = (((r * s) - w) / matrixH[m + 1, m]) + matrixH[m, m + 1]; + q = matrixH[m + 1, m + 1] - z - r - s; + r = matrixH[m + 2, m + 1]; + s = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + p = p / s; + q = q / s; + r = r / s; + + if (m == l) + { + break; + } + + if (Math.Abs(matrixH[m, m - 1]) * (Math.Abs(q) + Math.Abs(r)) < eps * (Math.Abs(p) * (Math.Abs(matrixH[m - 1, m - 1]) + Math.Abs(z) + Math.Abs(matrixH[m + 1, m + 1])))) + { + break; + } + + m--; + } + + for (var i = m + 2; i <= n; i++) + { + matrixH[i, i - 2] = 0.0; + if (i > m + 2) + { + matrixH[i, i - 3] = 0.0; + } + } + + // Double QR step involving rows l:n and columns m:n + for (var k = m; k <= n - 1; k++) + { + bool notlast = k != n - 1; + + if (k != m) + { + p = matrixH[k, k - 1]; + q = matrixH[k + 1, k - 1]; + r = notlast ? matrixH[k + 2, k - 1] : 0.0; + x = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + if (x != 0.0) + { + p = p / x; + q = q / x; + r = r / x; + } + } + + if (x == 0.0) + { + break; + } + + s = Math.Sqrt((p * p) + (q * q) + (r * r)); + if (p < 0) + { + s = -s; + } + + if (s != 0.0) + { + if (k != m) + { + matrixH[k, k - 1] = (-s) * x; + } + else if (l != m) + { + matrixH[k, k - 1] = -matrixH[k, k - 1]; + } + + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // Row modification + for (var j = k; j < order; j++) + { + p = matrixH[k, j] + (q * matrixH[k + 1, j]); + + if (notlast) + { + p = p + (r * matrixH[k + 2, j]); + matrixH[k + 2, j] = matrixH[k + 2, j] - (p * z); + } + + matrixH[k, j] = matrixH[k, j] - (p * x); + matrixH[k + 1, j] = matrixH[k + 1, j] - (p * y); + } + + // Column modification + for (var i = 0; i <= Math.Min(n, k + 3); i++) + { + p = (x * matrixH[i, k]) + (y * matrixH[i, k + 1]); + + if (notlast) + { + p = p + (z * matrixH[i, k + 2]); + matrixH[i, k + 2] = matrixH[i, k + 2] - (p * r); + } + + matrixH[i, k] = matrixH[i, k] - p; + matrixH[i, k + 1] = matrixH[i, k + 1] - (p * q); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + p = (x * a[(k * order) + i]) + (y * a[((k + 1) * order) + i]); + + if (notlast) + { + p = p + (z * a[((k + 2) * order) + i]); + a[((k + 2) * order) + i] -= p * r; + } + + a[(k * order) + i] -= p; + a[((k + 1) * order) + i] -= p * q; + } + } // (s != 0) + } // k loop + } // check convergence + } // while (n >= low) + + // Backsubstitute to find vectors of upper triangular form + if (norm == 0.0) + { + return; + } + + for (n = order - 1; n >= 0; n--) + { + double t; + + p = d[n]; + q = e[n]; + + // Real vector + if (q == 0.0) + { + var l = n; + matrixH[n, n] = 1.0; + for (var i = n - 1; i >= 0; i--) + { + w = matrixH[i, i] - p; + r = 0.0; + for (var j = l; j <= n; j++) + { + r = r + (matrixH[i, j] * matrixH[j, n]); + } + + if (e[i] < 0.0) + { + z = w; + s = r; + } + else + { + l = i; + if (e[i] == 0.0) + { + if (w != 0.0) + { + matrixH[i, n] = (-r) / w; + } + else + { + matrixH[i, n] = (-r) / (eps * norm); + } + + // Solve real equations + } + else + { + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + q = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]); + t = ((x * s) - (z * r)) / q; + matrixH[i, n] = t; + if (Math.Abs(x) > Math.Abs(z)) + { + matrixH[i + 1, n] = (-r - (w * t)) / x; + } + else + { + matrixH[i + 1, n] = (-s - (y * t)) / z; + } + } + + // Overflow control + t = Math.Abs(matrixH[i, n]); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + + // Complex vector + } + else if (q < 0) + { + var l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (Math.Abs(matrixH[n, n - 1]) > Math.Abs(matrixH[n - 1, n])) + { + matrixH[n - 1, n - 1] = q / matrixH[n, n - 1]; + matrixH[n - 1, n] = (-(matrixH[n, n] - p)) / matrixH[n, n - 1]; + } + else + { + var res = Cdiv(0.0, -matrixH[n - 1, n], matrixH[n - 1, n - 1] - p, q); + matrixH[n - 1, n - 1] = res.Real; + matrixH[n - 1, n] = res.Imaginary; + } + + matrixH[n, n - 1] = 0.0; + matrixH[n, n] = 1.0; + for (var i = n - 2; i >= 0; i--) + { + double ra = 0.0; + double sa = 0.0; + for (var j = l; j <= n; j++) + { + ra = ra + (matrixH[i, j] * matrixH[j, n - 1]); + sa = sa + (matrixH[i, j] * matrixH[j, n]); + } + + w = matrixH[i, i] - p; + + if (e[i] < 0.0) + { + z = w; + r = ra; + s = sa; + } + else + { + l = i; + if (e[i] == 0.0) + { + var res = Cdiv(-ra, -sa, w, q); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + } + else + { + // Solve complex equations + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + + double vr = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]) - (q * q); + double vi = (d[i] - p) * 2.0 * q; + if ((vr == 0.0) && (vi == 0.0)) + { + vr = eps * norm * (Math.Abs(w) + Math.Abs(q) + Math.Abs(x) + Math.Abs(y) + Math.Abs(z)); + } + + var res = Cdiv((x * r) - (z * ra) + (q * sa), (x * s) - (z * sa) - (q * ra), vr, vi); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + if (Math.Abs(x) > (Math.Abs(z) + Math.Abs(q))) + { + matrixH[i + 1, n - 1] = (-ra - (w * matrixH[i, n - 1]) + (q * matrixH[i, n])) / x; + matrixH[i + 1, n] = (-sa - (w * matrixH[i, n]) - (q * matrixH[i, n - 1])) / x; + } + else + { + res = Cdiv(-r - (y * matrixH[i, n - 1]), -s - (y * matrixH[i, n]), z, q); + matrixH[i + 1, n - 1] = res.Real; + matrixH[i + 1, n] = res.Imaginary; + } + } + + // Overflow control + t = Math.Max(Math.Abs(matrixH[i, n - 1]), Math.Abs(matrixH[i, n])); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n - 1] = matrixH[j, n - 1] / t; + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j >= 0; j--) + { + for (var i = 0; i < order; i++) + { + z = 0.0; + for (var k = 0; k <= j; k++) + { + z = z + (a[(k * order) + i] * matrixH[k, j]); + } + + a[(j * order) + i] = z; + } + } + } + + /// + /// Complex scalar division X/Y. + /// + /// Real part of X + /// Imaginary part of X + /// Real part of Y + /// Imaginary part of Y + /// Division result as a number. + private static Complex Cdiv(double xreal, double ximag, double yreal, double yimag) + { + if (Math.Abs(yimag) < Math.Abs(yreal)) + { + return new Complex((xreal + (ximag * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal))), (ximag - (xreal * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal)))); + } + + return new Complex((ximag + (xreal * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag))), (-xreal + (ximag * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag)))); + } + + /// + /// 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"); + } + + // 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 (VectorEv.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VectorEv.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = VectorEv.Count; + var tmp = new double[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + double value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i] * input.At(i, k); + } + + value /= VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + double value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j, k] = value; + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD 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 m matrix + // Check that b is a column vector with m entries + if (VectorEv.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VectorEv.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VT * b; + var order = VectorEv.Count; + var tmp = new double[order]; + double value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i] * input[i]; + } + + value /= VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override double MultiplyT(double val1, double val2) + { + return val1 * val2; + } + } +} \ No newline at end of file diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/DenseGramSchmidt.cs b/src/Numerics/LinearAlgebra/Double/Factorization/DenseGramSchmidt.cs new file mode 100644 index 00000000..3b190b9f --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,237 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using Generic; + using Generic.Factorization; + using Properties; + using Threading; + + /// + /// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. + /// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. + /// + /// + /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. + /// + public class DenseGramSchmidt : GramSchmidt + { + /// + /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public DenseGramSchmidt(DenseMatrix 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); + Factorize(((DenseMatrix)MatrixQ).Data, MatrixQ.RowCount, MatrixQ.ColumnCount, ((DenseMatrix)MatrixR).Data); + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(double[] q, int rowsQ, int columnsQ, double[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i] * q[(k * rowsQ) + i]; + } + + norm = Math.Sqrt(norm); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + int k1 = k; + int j1 = j; + var dot = CommonParallel.Aggregate(0, rowsQ, index => q[(k1 * rowsQ) + index] * q[(j1 * rowsQ) + index]); + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// 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 dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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 (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 dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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 double MultiplyT(double val1, double 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(double val1) + { + return Math.Abs(val1); + } + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs index c6acd360..3850d09f 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs @@ -113,11 +113,11 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization if (e[i] > 0) { - MatrixD[i, i + 1] = e[i]; + MatrixD.At(i, i + 1, e[i]); } else if (e[i] < 0) { - MatrixD[i, i - 1] = e[i]; + MatrixD.At(i, i - 1, e[i]); } } @@ -156,9 +156,9 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization e[i] = d[i - 1]; for (var j = 0; j < i; j++) { - d[j] = MatrixEv[i - 1, j]; - MatrixEv[i, j] = 0.0; - MatrixEv[j, i] = 0.0; + d[j] = MatrixEv.At(i - 1, j); + MatrixEv.At(i, j, 0.0); + MatrixEv.At(j, i, 0.0); } } else @@ -190,13 +190,13 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization for (var j = 0; j < i; j++) { f = d[j]; - MatrixEv[j, i] = f; - g = e[j] + (MatrixEv[j, j] * f); + MatrixEv.At(j, i, f); + g = e[j] + (MatrixEv.At(j, j) * f); for (var k = j + 1; k <= i - 1; k++) { - g += MatrixEv[k, j] * d[k]; - e[k] += MatrixEv[k, j] * f; + g += MatrixEv.At(k, j) * d[k]; + e[k] += MatrixEv.At(k, j) * f; } e[j] = g; @@ -224,11 +224,11 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization for (var k = j; k <= i - 1; k++) { - MatrixEv[k, j] -= (f * e[k]) + (g * d[k]); + MatrixEv.At(k, j, MatrixEv.At(k, j) - (f * e[k]) - (g * d[k])); } - d[j] = MatrixEv[i - 1, j]; - MatrixEv[i, j] = 0.0; + d[j] = MatrixEv.At(i - 1, j); + MatrixEv.At(i, j, 0.0); } } @@ -238,14 +238,14 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization // Accumulate transformations. for (var i = 0; i < order - 1; i++) { - MatrixEv[order - 1, i] = MatrixEv[i, i]; - MatrixEv[i, i] = 1.0; + MatrixEv.At(order - 1, i, MatrixEv.At(i, i)); + MatrixEv.At(i, i, 1.0); var h = d[i + 1]; if (h != 0.0) { for (var k = 0; k <= i; k++) { - d[k] = MatrixEv[k, i + 1] / h; + d[k] = MatrixEv.At(k, i + 1) / h; } for (var j = 0; j <= i; j++) @@ -253,29 +253,29 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization var g = 0.0; for (var k = 0; k <= i; k++) { - g += MatrixEv[k, i + 1] * MatrixEv[k, j]; + g += MatrixEv.At(k, i + 1) * MatrixEv.At(k, j); } for (var k = 0; k <= i; k++) { - MatrixEv[k, j] -= g * d[k]; + MatrixEv.At(k, j, MatrixEv.At(k, j) - g * d[k]); } } } for (var k = 0; k <= i; k++) { - MatrixEv[k, i + 1] = 0.0; + MatrixEv.At(k, i + 1, 0.0); } } for (var j = 0; j < order; j++) { - d[j] = MatrixEv[order - 1, j]; - MatrixEv[order - 1, j] = 0.0; + d[j] = MatrixEv.At(order - 1, j); + MatrixEv.At(order - 1, j, 0.0); } - MatrixEv[order - 1, order - 1] = 1.0; + MatrixEv.At(order - 1, order - 1, 1.0); e[0] = 0.0; } @@ -373,9 +373,9 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization // Accumulate transformation. for (var k = 0; k < order; k++) { - h = MatrixEv[k, i + 1]; - MatrixEv[k, i + 1] = (s * MatrixEv[k, i]) + (c * h); - MatrixEv[k, i] = (c * MatrixEv[k, i]) - (s * h); + h = MatrixEv.At(k, i + 1); + MatrixEv.At(k, i + 1, (s * MatrixEv.At(k, i)) + (c * h)); + MatrixEv.At(k, i, (c * MatrixEv.At(k, i)) - (s * h)); } } @@ -417,9 +417,9 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization d[i] = p; for (var j = 0; j < order; j++) { - p = MatrixEv[j, i]; - MatrixEv[j, i] = MatrixEv[j, k]; - MatrixEv[j, k] = p; + p = MatrixEv.At(j, i); + MatrixEv.At(j, i, MatrixEv.At(j, k)); + MatrixEv.At(j, k, p); } } } @@ -508,7 +508,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization { for (var j = 0; j < order; j++) { - MatrixEv[i, j] = i == j ? 1.0 : 0.0; + MatrixEv.At(i, j, i == j ? 1.0 : 0.0); } } @@ -526,14 +526,14 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization var g = 0.0; for (var i = m; i < order; i++) { - g += ort[i] * MatrixEv[i, j]; + g += ort[i] * MatrixEv.At(i, j); } // Double division avoids possible underflow g = (g / ort[m]) / matrixH[m, m - 1]; for (var i = m; i < order; i++) { - MatrixEv[i, j] += g * ort[i]; + MatrixEv.At(i, j, MatrixEv.At(i, j) + g * ort[i]); } } } @@ -663,9 +663,9 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization // Accumulate transformations for (var i = 0; i < order; i++) { - z = MatrixEv[i, n - 1]; - MatrixEv[i, n - 1] = (q * z) + (p * MatrixEv[i, n]); - MatrixEv[i, n] = (q * MatrixEv[i, n]) - (p * z); + z = MatrixEv.At(i, n - 1); + MatrixEv.At(i, n - 1, (q * z) + (p * MatrixEv.At(i, n))); + MatrixEv.At(i, n, (q * MatrixEv.At(i, n)) - (p * z)); } // Complex pair @@ -853,16 +853,16 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization // Accumulate transformations for (var i = 0; i < order; i++) { - p = (x * MatrixEv[i, k]) + (y * MatrixEv[i, k + 1]); + p = (x * MatrixEv.At(i, k)) + (y * MatrixEv.At(i, k + 1)); if (notlast) { - p = p + (z * MatrixEv[i, k + 2]); - MatrixEv[i, k + 2] = MatrixEv[i, k + 2] - (p * r); + p = p + (z * MatrixEv.At(i, k + 2)); + MatrixEv.At(i, k + 2, MatrixEv.At(i, k + 2) - (p * r)); } - MatrixEv[i, k] = MatrixEv[i, k] - p; - MatrixEv[i, k + 1] = MatrixEv[i, k + 1] - (p * q); + MatrixEv.At(i, k, MatrixEv.At(i, k) - p); + MatrixEv.At(i, k + 1, MatrixEv.At(i, k + 1) - (p * q)); } } // (s != 0) } // k loop @@ -1046,10 +1046,10 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization z = 0.0; for (var k = 0; k <= j; k++) { - z = z + (MatrixEv[i, k] * matrixH[k, j]); + z = z + (MatrixEv.At(i, k) * matrixH[k, j]); } - MatrixEv[i, j] = z; + MatrixEv.At(i, j, z); } } } diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs b/src/Numerics/LinearAlgebra/Double/Factorization/UserGramSchmidt.cs similarity index 87% rename from src/Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs rename to src/Numerics/LinearAlgebra/Double/Factorization/UserGramSchmidt.cs index 7fdc0cd4..417a5922 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/GramSchmidt.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/UserGramSchmidt.cs @@ -1,4 +1,4 @@ -// +// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics @@ -42,17 +42,17 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. /// - public class GramSchmidt : QR + public class UserGramSchmidt : GramSchmidt { /// - /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// Initializes a new instance of the class. This object creates an orthogonal matrix /// using the modified Gram-Schmidt method. /// /// The matrix to factor. /// If is null. /// If row count is less then column count /// If is rank deficient - public GramSchmidt(Matrix matrix) + public UserGramSchmidt(Matrix matrix) { if (matrix == null) { @@ -94,44 +94,6 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } } - /// - /// Gets a value indicating whether the matrix is full rank or not. - /// - /// true if the matrix is full rank; otherwise false. - public override bool IsFullRank - { - get - { - return true; - } - } - - /// - /// Gets the determinant of the matrix for which the QR matrix was computed. - /// - public override double Determinant - { - get - { - if (MatrixQ.RowCount != MatrixQ.ColumnCount) - { - throw new ArgumentException(Resources.ArgumentMatrixSquare); - } - - var det = 1.0; - for (var i = 0; i < MatrixR.ColumnCount; i++) - { - det *= MatrixR.At(i, i); - if (Math.Abs(MatrixR.At(i, i)).AlmostEqualInDecimalPlaces(0.0, 15)) - { - return 0; - } - } - - return Math.Abs(det); - } - } - /// /// Solves a system of linear equations, AX = B, with A QR factorized. /// diff --git a/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs index 4410e705..bf600e04 100644 --- a/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs +++ b/src/Numerics/LinearAlgebra/Double/Solvers/Iterative/MlkBiCgStab.cs @@ -35,8 +35,8 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Iterative using System.Diagnostics; using System.Linq; using Distributions; - using Factorization; using Generic; + using Generic.Factorization; using Generic.Solvers; using Generic.Solvers.Preconditioners; using Generic.Solvers.Status; @@ -635,7 +635,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Solvers.Iterative } // Compute the orthogonalization. - var gs = new GramSchmidt(matrix); + var gs = matrix.GramSchmidt(); var orthogonalMatrix = gs.Q; // Now transfer this to vectors diff --git a/src/Numerics/LinearAlgebra/Double/SparseVector.cs b/src/Numerics/LinearAlgebra/Double/SparseVector.cs index da281ae0..6ef0ad24 100644 --- a/src/Numerics/LinearAlgebra/Double/SparseVector.cs +++ b/src/Numerics/LinearAlgebra/Double/SparseVector.cs @@ -29,6 +29,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double using System; using System.Collections.Generic; using System.Globalization; + using System.Linq; using Distributions; using Generic; using NumberTheory; @@ -1237,6 +1238,11 @@ namespace MathNet.Numerics.LinearAlgebra.Double return 0.0; } + if (2.0 == p) + { + return _nonZeroValues.Aggregate(0.0, SpecialFunctions.Hypotenuse); + } + if (Double.IsPositiveInfinity(p)) { return CommonParallel.Select(0, NonZerosCount, (index, localData) => Math.Max(localData, Math.Abs(_nonZeroValues[index])), Math.Max); diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs index f2a26d7a..faff97fb 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs @@ -99,21 +99,45 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization { if (typeof(T) == typeof(double)) { + var dense = matrix as LinearAlgebra.Double.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Double.Factorization.DenseEvd(dense) as Evd; + } + return new LinearAlgebra.Double.Factorization.UserEvd(matrix as Matrix) as Evd; } if (typeof(T) == typeof(float)) { + var dense = matrix as LinearAlgebra.Single.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Single.Factorization.DenseEvd(dense) as Evd; + } + return new LinearAlgebra.Single.Factorization.UserEvd(matrix as Matrix) as Evd; } if (typeof(T) == typeof(Complex)) { + var dense = matrix as LinearAlgebra.Complex.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex.Factorization.DenseEvd(dense) as Evd; + } + return new LinearAlgebra.Complex.Factorization.UserEvd(matrix as Matrix) as Evd; } if (typeof(T) == typeof(Complex32)) { + var dense = matrix as LinearAlgebra.Complex32.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex32.Factorization.DenseEvd(dense) as Evd; + } + return new LinearAlgebra.Complex32.Factorization.UserEvd(matrix as Matrix) as Evd; } diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs index 0f89e881..520cf252 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs @@ -79,29 +79,9 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization /// The matrix to factor. /// The QR decomposition object. /// Supported data types are double, single, , and . - public static QR GramSchmidt(this Matrix matrix) where T : struct, IEquatable, IFormattable + public static GramSchmidt GramSchmidt(this Matrix matrix) where T : struct, IEquatable, IFormattable { - if (typeof(T) == typeof(double)) - { - return new LinearAlgebra.Double.Factorization.GramSchmidt(matrix as Matrix) as QR; - } - - if (typeof(T) == typeof(float)) - { - return new LinearAlgebra.Single.Factorization.GramSchmidt(matrix as Matrix) as QR; - } - - if (typeof(T) == typeof(Complex)) - { - return new LinearAlgebra.Complex.Factorization.GramSchmidt(matrix as Matrix) as QR; - } - - if (typeof(T) == typeof(Complex32)) - { - return new LinearAlgebra.Complex32.Factorization.GramSchmidt(matrix as Matrix) as QR; - } - - throw new NotImplementedException(); + return Factorization.GramSchmidt.Create(matrix); } /// diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/GramSchmidt.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/GramSchmidt.cs new file mode 100644 index 00000000..94b43bd7 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/GramSchmidt.cs @@ -0,0 +1,138 @@ +// +// 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.Generic.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Numerics; + using Properties; + + /// + /// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. + /// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. + /// + /// + /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. + /// + /// Supported data types are double, single, , and . + public abstract class GramSchmidt : QR + where T : struct, IEquatable, IFormattable + { + /// + /// Internal method which routes the call to perform the QR factorization to the appropriate class. + /// + /// The matrix to factor. + /// A QR factorization object. + new internal static GramSchmidt Create(Matrix matrix) + { + if (typeof(T) == typeof(double)) + { + var dense = matrix as LinearAlgebra.Double.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Double.Factorization.DenseGramSchmidt(dense) as GramSchmidt; + } + + return new LinearAlgebra.Double.Factorization.UserGramSchmidt(matrix as Matrix) as GramSchmidt; + } + + if (typeof(T) == typeof(float)) + { + var dense = matrix as LinearAlgebra.Single.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Single.Factorization.DenseGramSchmidt(dense) as GramSchmidt; + } + + return new LinearAlgebra.Single.Factorization.UserGramSchmidt(matrix as Matrix) as GramSchmidt; + } + + if (typeof(T) == typeof(Complex)) + { + var dense = matrix as LinearAlgebra.Complex.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex.Factorization.DenseGramSchmidt(dense) as GramSchmidt; + } + + return new LinearAlgebra.Complex.Factorization.UserGramSchmidt(matrix as Matrix) as GramSchmidt; + } + + if (typeof(T) == typeof(Complex32)) + { + var dense = matrix as LinearAlgebra.Complex32.DenseMatrix; + if (dense != null) + { + return new LinearAlgebra.Complex32.Factorization.DenseGramSchmidt(dense) as GramSchmidt; + } + + return new LinearAlgebra.Complex32.Factorization.UserGramSchmidt(matrix as Matrix) as GramSchmidt; + } + + throw new NotImplementedException(); + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public sealed override bool IsFullRank + { + get + { + return true; + } + } + + /// + /// Gets the absolute determinant value 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 = OneValueT; + for (var i = 0; i < MatrixR.ColumnCount; i++) + { + det = MultiplyT(det, MatrixR.At(i, i)); + if (AbsoluteT(MatrixR.At(i, i)).AlmostEqualInDecimalPlaces(0.0, (typeof(T) == typeof(float) || typeof(T) == typeof(Complex32)) ? 7 : 15)) + { + return 0; + } + } + + return AbsoluteT(det); + } + } + } +} diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs index cf45704e..cc36babe 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/QR.cs @@ -255,7 +255,7 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization /// Gets value of type T equal to one /// /// One value - private static T OneValueT + protected static T OneValueT { get { diff --git a/src/Numerics/LinearAlgebra/Single/Factorization/DenseEvd.cs b/src/Numerics/LinearAlgebra/Single/Factorization/DenseEvd.cs new file mode 100644 index 00000000..712c4ff2 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Single/Factorization/DenseEvd.cs @@ -0,0 +1,1238 @@ +// +// 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.Single.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Numerics; + using Properties; + + /// + /// Eigenvalues and eigenvectors of a real matrix. + /// + /// + /// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is + /// diagonal and the eigenvector matrix V is orthogonal. + /// I.e. A = V*D*V' and V*VT=I. + /// If A is not symmetric, then the eigenvalue matrix D is block diagonal + /// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, + /// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The + /// columns of V represent the eigenvectors in the sense that A*V = V*D, + /// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly + /// conditioned, or even singular, so the validity of the equation + /// A = V*D*Inverse(V) depends upon V.cond(). + /// + public class DenseEvd : Evd + { + /// + /// Initializes a new instance of the class. This object will compute the + /// the eigenvalue decomposition when the constructor is called and cache it's decomposition. + /// + /// The matrix to factor. + /// If is null. + /// If EVD algorithm failed to converge with matrix . + public DenseEvd(DenseMatrix matrix) + { + if (matrix == null) + { + throw new ArgumentNullException("matrix"); + } + + if (matrix.RowCount != matrix.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSquare); + } + + var order = matrix.RowCount; + + // Initialize matricies for eigenvalues and eigenvectors + MatrixEv = matrix.CreateMatrix(order, order); + MatrixD = matrix.CreateMatrix(order, order); + VectorEv = new LinearAlgebra.Complex.DenseVector(order); + + IsSymmetric = true; + + for (var i = 0; i < order & IsSymmetric; i++) + { + for (var j = 0; j < order & IsSymmetric; j++) + { + IsSymmetric &= matrix[i, j] == matrix[j, i]; + } + } + + var d = new float[order]; + var e = new float[order]; + + if (IsSymmetric) + { + matrix.CopyTo(MatrixEv); + d = MatrixEv.Row(order - 1).ToArray(); + + SymmetricTridiagonalize(((DenseMatrix)MatrixEv).Data, d, e, order); + SymmetricDiagonalize(((DenseMatrix)MatrixEv).Data, d, e, order); + } + else + { + var matrixH = matrix.ToArray(); + + NonsymmetricReduceToHessenberg(((DenseMatrix)MatrixEv).Data, matrixH, order); + NonsymmetricReduceHessenberToRealSchur(((DenseMatrix)MatrixEv).Data, matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + MatrixD.At(i, i, d[i]); + + if (e[i] > 0) + { + MatrixD.At(i, i + 1, e[i]); + } + else if (e[i] < 0) + { + MatrixD.At(i, i - 1, e[i]); + } + } + + for (var i = 0; i < order; i++) + { + VectorEv[i] = new Complex(d[i], e[i]); + } + } + + /// + /// Symmetric Householder reduction to tridiagonal form. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tred2 by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricTridiagonalize(float[] a, float[] d, float[] e, int order) + { + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0f; + var h = 0.0f; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(d[k]); + } + + if (scale == 0.0f) + { + e[i] = d[i - 1]; + for (var j = 0; j < i; j++) + { + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0f; + a[(i * order) + j] = 0.0f; + } + } + else + { + // Generate Householder vector. + for (var k = 0; k < i; k++) + { + d[k] /= scale; + h += d[k] * d[k]; + } + + var f = d[i - 1]; + var g = (float)Math.Sqrt(h); + if (f > 0) + { + g = -g; + } + + e[i] = scale * g; + h = h - (f * g); + d[i - 1] = f - g; + + for (var j = 0; j < i; j++) + { + e[j] = 0.0f; + } + + // Apply similarity transformation to remaining columns. + for (var j = 0; j < i; j++) + { + f = d[j]; + a[(i * order) + j] = f; + g = e[j] + (a[(j * order) + j] * f); + + for (var k = j + 1; k <= i - 1; k++) + { + g += a[(j * order) + k] * d[k]; + e[k] += a[(j * order) + k] * f; + } + + e[j] = g; + } + + f = 0.0f; + + for (var j = 0; j < i; j++) + { + e[j] /= h; + f += e[j] * d[j]; + } + + var hh = f / (h + h); + + for (var j = 0; j < i; j++) + { + e[j] -= hh * d[j]; + } + + for (var j = 0; j < i; j++) + { + f = d[j]; + g = e[j]; + + for (var k = j; k <= i - 1; k++) + { + a[(j * order) + k] -= (f * e[k]) + (g * d[k]); + } + + d[j] = a[(j * order) + i - 1]; + a[(j * order) + i] = 0.0f; + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + a[(i * order) + order - 1] = a[(i * order) + i]; + a[(i * order) + i] = 1.0f; + var h = d[i + 1]; + if (h != 0.0f) + { + for (var k = 0; k <= i; k++) + { + d[k] = a[((i + 1) * order) + k] / h; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0f; + for (var k = 0; k <= i; k++) + { + g += a[((i + 1) * order) + k] * a[(j * order) + k]; + } + + for (var k = 0; k <= i; k++) + { + a[(j * order) + k] -= g * d[k]; + } + } + } + + for (var k = 0; k <= i; k++) + { + a[((i + 1) * order) + k] = 0.0f; + } + } + + for (var j = 0; j < order; j++) + { + d[j] = a[(j * order) + order - 1]; + a[(j * order) + order - 1] = 0.0f; + } + + a[(order * order) - 1] = 1.0f; + e[0] = 0.0f; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// Data array of matrix V (eigenvectors) + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedures tql2, by + /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for + /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void SymmetricDiagonalize(float[] a, float[] d, float[] e, int order) + { + const int Maxiter = 1000; + + for (var i = 1; i < order; i++) + { + e[i - 1] = e[i]; + } + + e[order - 1] = 0.0f; + + var f = 0.0f; + var tst1 = 0.0f; + var eps = Precision.DoubleMachinePrecision; + for (var l = 0; l < order; l++) + { + // Find small subdiagonal element + tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); + var m = l; + while (m < order) + { + if (Math.Abs(e[m]) <= eps * tst1) + { + break; + } + + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) + { + var iter = 0; + do + { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + var g = d[l]; + var p = (d[l + 1] - g) / (2.0f * e[l]); + var r = SpecialFunctions.Hypotenuse(p, 1.0f); + if (p < 0) + { + r = -r; + } + + d[l] = e[l] / (p + r); + d[l + 1] = e[l] * (p + r); + + var dl1 = d[l + 1]; + var h = g - d[l]; + for (var i = l + 2; i < order; i++) + { + d[i] -= h; + } + + f = f + h; + + // Implicit QL transformation. + p = d[m]; + var c = 1.0f; + var c2 = c; + var c3 = c; + var el1 = e[l + 1]; + var s = 0.0f; + var s2 = 0.0f; + for (var i = m - 1; i >= l; i--) + { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = SpecialFunctions.Hypotenuse(p, e[i]); + e[i + 1] = s * r; + s = e[i] / r; + c = p / r; + p = (c * d[i]) - (s * g); + d[i + 1] = h + (s * ((c * g) + (s * d[i]))); + + // Accumulate transformation. + for (var k = 0; k < order; k++) + { + h = a[((i + 1) * order) + k]; + a[((i + 1) * order) + k] = (s * a[(i * order) + k]) + (c * h); + a[(i * order) + k] = (c * a[(i * order) + k]) - (s * h); + } + } + + p = (-s) * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. If too many iterations have been performed, + // throw exception that Convergence Failed + if (iter >= Maxiter) + { + throw new ArgumentException(Resources.ConvergenceFailed); + } + } + while (Math.Abs(e[l]) > eps * tst1); + } + + d[l] = d[l] + f; + e[l] = 0.0f; + } + + // Sort eigenvalues and corresponding vectors. + for (var i = 0; i < order - 1; i++) + { + var k = i; + var p = d[i]; + for (var j = i + 1; j < order; j++) + { + if (d[j] < p) + { + k = j; + p = d[j]; + } + } + + if (k != i) + { + d[k] = d[i]; + d[i] = p; + for (var j = 0; j < order; j++) + { + p = a[(i * order) + j]; + a[(i * order) + j] = a[(k * order) + j]; + a[(k * order) + j] = p; + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Order of initial matrix + /// This is derived from the Algol procedures orthes and ortran, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutines in EISPACK. + private static void NonsymmetricReduceToHessenberg(float[] a, float[,] matrixH, int order) + { + var ort = new float[order]; + + for (var m = 1; m < order - 1; m++) + { + // Scale column. + var scale = 0.0f; + for (var i = m; i < order; i++) + { + scale = scale + Math.Abs(matrixH[i, m - 1]); + } + + if (scale != 0.0f) + { + // Compute Householder transformation. + var h = 0.0f; + for (var i = order - 1; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i] * ort[i]; + } + + var g = (float)Math.Sqrt(h); + if (ort[m] > 0) + { + g = -g; + } + + h = h - (ort[m] * g); + ort[m] = ort[m] - g; + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = 0.0f; + for (var i = order - 1; i >= m; i--) + { + f += ort[i] * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i < order; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i < order; i++) + { + var f = 0.0f; + for (var j = order - 1; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j < order; j++) + { + matrixH[i, j] -= f * ort[j]; + } + } + + ort[m] = scale * ort[m]; + matrixH[m, m - 1] = scale * g; + } + } + + // Accumulate transformations (Algol's ortran). + for (var i = 0; i < order; i++) + { + for (var j = 0; j < order; j++) + { + a[(j * order) + i] = i == j ? 1.0f : 0.0f; + } + } + + for (var m = order - 2; m >= 1; m--) + { + if (matrixH[m, m - 1] != 0.0f) + { + for (var i = m + 1; i < order; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j < order; j++) + { + var g = 0.0f; + for (var i = m; i < order; i++) + { + g += ort[i] * a[(j * order) + i]; + } + + // Double division avoids possible underflow + g = (g / ort[m]) / matrixH[m, m - 1]; + for (var i = m; i < order; i++) + { + a[(j * order) + i] += g * ort[i]; + } + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// Data array of matrix V (eigenvectors) + /// Array for internal storage of nonsymmetric Hessenberg form. + /// Arrays for internal storage of real parts of eigenvalues + /// Arrays for internal storage of imaginary parts of eigenvalues + /// Order of initial matrix + /// This is derived from the Algol procedure hqr2, + /// by Martin and Wilkinson, Handbook for Auto. Comp., + /// Vol.ii-Linear Algebra, and the corresponding + /// Fortran subroutine in EISPACK. + private static void NonsymmetricReduceHessenberToRealSchur(float[] a, float[,] matrixH, float[] d, float[] e, int order) + { + // Initialize + var n = order - 1; + var eps = (float)Precision.SingleMachinePrecision; + var exshift = 0.0f; + float p = 0, q = 0, r = 0, s = 0, z = 0, w, x, y; + + // Store roots isolated by balanc and compute matrix norm + var norm = 0.0f; + for (var i = 0; i < order; i++) + { + for (var j = Math.Max(i - 1, 0); j < order; j++) + { + norm = norm + Math.Abs(matrixH[i, j]); + } + } + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= 0) + { + // Look for single small sub-diagonal element + var l = n; + while (l > 0) + { + s = Math.Abs(matrixH[l - 1, l - 1]) + Math.Abs(matrixH[l, l]); + + if (s == 0.0f) + { + s = norm; + } + + if (Math.Abs(matrixH[l, l - 1]) < eps * s) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] = matrixH[n, n] + exshift; + d[n] = matrixH[n, n]; + e[n] = 0.0f; + n--; + iter = 0; + + // Two roots found + } + else if (l == n - 1) + { + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + p = (matrixH[n - 1, n - 1] - matrixH[n, n]) / 2.0f; + q = (p * p) + w; + z = (float)Math.Sqrt(Math.Abs(q)); + matrixH[n, n] = matrixH[n, n] + exshift; + matrixH[n - 1, n - 1] = matrixH[n - 1, n - 1] + exshift; + x = matrixH[n, n]; + + // Real pair + if (q >= 0) + { + if (p >= 0) + { + z = p + z; + } + else + { + z = p - z; + } + + d[n - 1] = x + z; + + d[n] = d[n - 1]; + if (z != 0.0f) + { + d[n] = x - (w / z); + } + + e[n - 1] = 0.0f; + e[n] = 0.0f; + x = matrixH[n, n - 1]; + s = Math.Abs(x) + Math.Abs(z); + p = x / s; + q = z / s; + r = (float)Math.Sqrt((p * p) + (q * q)); + p = p / r; + q = q / r; + + // Row modification + for (var j = n - 1; j < order; j++) + { + z = matrixH[n - 1, j]; + matrixH[n - 1, j] = (q * z) + (p * matrixH[n, j]); + matrixH[n, j] = (q * matrixH[n, j]) - (p * z); + } + + // Column modification + for (var i = 0; i <= n; i++) + { + z = matrixH[i, n - 1]; + matrixH[i, n - 1] = (q * z) + (p * matrixH[i, n]); + matrixH[i, n] = (q * matrixH[i, n]) - (p * z); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + z = a[((n - 1) * order) + i]; + a[((n - 1) * order) + i] = (q * z) + (p * a[(n * order) + i]); + a[(n * order) + i] = (q * a[(n * order) + i]) - (p * z); + } + + // Complex pair + } + else + { + d[n - 1] = x + p; + d[n] = x + p; + e[n - 1] = z; + e[n] = -z; + } + + n = n - 2; + iter = 0; + + // No convergence yet + } + else + { + // Form shift + x = matrixH[n, n]; + y = 0.0f; + w = 0.0f; + if (l < n) + { + y = matrixH[n - 1, n - 1]; + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + } + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + exshift += x; + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= x; + } + + s = Math.Abs(matrixH[n, n - 1]) + Math.Abs(matrixH[n - 1, n - 2]); + x = y = 0.75f * s; + w = (-0.4375f) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + s = (y - x) / 2.0f; + s = (s * s) + w; + if (s > 0) + { + s = (float)Math.Sqrt(s); + if (y < x) + { + s = -s; + } + + s = x - (w / (((y - x) / 2.0f) + s)); + for (var i = 0; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + x = y = w = 0.964f; + } + } + + iter = iter + 1; // (Could check iteration count here.) + + // Look for two consecutive small sub-diagonal elements + var m = n - 2; + while (m >= l) + { + z = matrixH[m, m]; + r = x - z; + s = y - z; + p = (((r * s) - w) / matrixH[m + 1, m]) + matrixH[m, m + 1]; + q = matrixH[m + 1, m + 1] - z - r - s; + r = matrixH[m + 2, m + 1]; + s = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + p = p / s; + q = q / s; + r = r / s; + + if (m == l) + { + break; + } + + if (Math.Abs(matrixH[m, m - 1]) * (Math.Abs(q) + Math.Abs(r)) < eps * (Math.Abs(p) * (Math.Abs(matrixH[m - 1, m - 1]) + Math.Abs(z) + Math.Abs(matrixH[m + 1, m + 1])))) + { + break; + } + + m--; + } + + for (var i = m + 2; i <= n; i++) + { + matrixH[i, i - 2] = 0.0f; + if (i > m + 2) + { + matrixH[i, i - 3] = 0.0f; + } + } + + // Double QR step involving rows l:n and columns m:n + for (var k = m; k <= n - 1; k++) + { + bool notlast = k != n - 1; + + if (k != m) + { + p = matrixH[k, k - 1]; + q = matrixH[k + 1, k - 1]; + r = notlast ? matrixH[k + 2, k - 1] : 0.0f; + x = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + if (x != 0.0f) + { + p = p / x; + q = q / x; + r = r / x; + } + } + + if (x == 0.0f) + { + break; + } + + s = (float)Math.Sqrt((p * p) + (q * q) + (r * r)); + if (p < 0) + { + s = -s; + } + + if (s != 0.0f) + { + if (k != m) + { + matrixH[k, k - 1] = (-s) * x; + } + else if (l != m) + { + matrixH[k, k - 1] = -matrixH[k, k - 1]; + } + + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // Row modification + for (var j = k; j < order; j++) + { + p = matrixH[k, j] + (q * matrixH[k + 1, j]); + + if (notlast) + { + p = p + (r * matrixH[k + 2, j]); + matrixH[k + 2, j] = matrixH[k + 2, j] - (p * z); + } + + matrixH[k, j] = matrixH[k, j] - (p * x); + matrixH[k + 1, j] = matrixH[k + 1, j] - (p * y); + } + + // Column modification + for (var i = 0; i <= Math.Min(n, k + 3); i++) + { + p = (x * matrixH[i, k]) + (y * matrixH[i, k + 1]); + + if (notlast) + { + p = p + (z * matrixH[i, k + 2]); + matrixH[i, k + 2] = matrixH[i, k + 2] - (p * r); + } + + matrixH[i, k] = matrixH[i, k] - p; + matrixH[i, k + 1] = matrixH[i, k + 1] - (p * q); + } + + // Accumulate transformations + for (var i = 0; i < order; i++) + { + p = (x * a[(k * order) + i]) + (y * a[((k + 1) * order) + i]); + + if (notlast) + { + p = p + (z * a[((k + 2) * order) + i]); + a[((k + 2) * order) + i] -= p * r; + } + + a[(k * order) + i] -= p; + a[((k + 1) * order) + i] -= p * q; + } + } // (s != 0) + } // k loop + } // check convergence + } // while (n >= low) + + // Backsubstitute to find vectors of upper triangular form + if (norm == 0.0f) + { + return; + } + + for (n = order - 1; n >= 0; n--) + { + float t; + + p = d[n]; + q = e[n]; + + // Real vector + if (q == 0.0f) + { + var l = n; + matrixH[n, n] = 1.0f; + for (var i = n - 1; i >= 0; i--) + { + w = matrixH[i, i] - p; + r = 0.0f; + for (var j = l; j <= n; j++) + { + r = r + (matrixH[i, j] * matrixH[j, n]); + } + + if (e[i] < 0.0f) + { + z = w; + s = r; + } + else + { + l = i; + if (e[i] == 0.0f) + { + if (w != 0.0f) + { + matrixH[i, n] = (-r) / w; + } + else + { + matrixH[i, n] = (-r) / (eps * norm); + } + + // Solve real equations + } + else + { + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + q = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]); + t = ((x * s) - (z * r)) / q; + matrixH[i, n] = t; + if (Math.Abs(x) > Math.Abs(z)) + { + matrixH[i + 1, n] = (-r - (w * t)) / x; + } + else + { + matrixH[i + 1, n] = (-s - (y * t)) / z; + } + } + + // Overflow control + t = Math.Abs(matrixH[i, n]); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + + // Complex vector + } + else if (q < 0) + { + var l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (Math.Abs(matrixH[n, n - 1]) > Math.Abs(matrixH[n - 1, n])) + { + matrixH[n - 1, n - 1] = q / matrixH[n, n - 1]; + matrixH[n - 1, n] = (-(matrixH[n, n] - p)) / matrixH[n, n - 1]; + } + else + { + var res = Cdiv(0.0f, -matrixH[n - 1, n], matrixH[n - 1, n - 1] - p, q); + matrixH[n - 1, n - 1] = res.Real; + matrixH[n - 1, n] = res.Imaginary; + } + + matrixH[n, n - 1] = 0.0f; + matrixH[n, n] = 1.0f; + for (var i = n - 2; i >= 0; i--) + { + float ra = 0.0f; + float sa = 0.0f; + for (var j = l; j <= n; j++) + { + ra = ra + (matrixH[i, j] * matrixH[j, n - 1]); + sa = sa + (matrixH[i, j] * matrixH[j, n]); + } + + w = matrixH[i, i] - p; + + if (e[i] < 0.0f) + { + z = w; + r = ra; + s = sa; + } + else + { + l = i; + if (e[i] == 0.0f) + { + var res = Cdiv(-ra, -sa, w, q); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + } + else + { + // Solve complex equations + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + + float vr = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]) - (q * q); + float vi = (d[i] - p) * 2.0f * q; + if ((vr == 0.0f) && (vi == 0.0f)) + { + vr = eps * norm * (Math.Abs(w) + Math.Abs(q) + Math.Abs(x) + Math.Abs(y) + Math.Abs(z)); + } + + var res = Cdiv((x * r) - (z * ra) + (q * sa), (x * s) - (z * sa) - (q * ra), vr, vi); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + if (Math.Abs(x) > (Math.Abs(z) + Math.Abs(q))) + { + matrixH[i + 1, n - 1] = (-ra - (w * matrixH[i, n - 1]) + (q * matrixH[i, n])) / x; + matrixH[i + 1, n] = (-sa - (w * matrixH[i, n]) - (q * matrixH[i, n - 1])) / x; + } + else + { + res = Cdiv(-r - (y * matrixH[i, n - 1]), -s - (y * matrixH[i, n]), z, q); + matrixH[i + 1, n - 1] = res.Real; + matrixH[i + 1, n] = res.Imaginary; + } + } + + // Overflow control + t = Math.Max(Math.Abs(matrixH[i, n - 1]), Math.Abs(matrixH[i, n])); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n - 1] = matrixH[j, n - 1] / t; + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + } + } + + // Back transformation to get eigenvectors of original matrix + for (var j = order - 1; j >= 0; j--) + { + for (var i = 0; i < order; i++) + { + z = 0.0f; + for (var k = 0; k <= j; k++) + { + z = z + (a[(k * order) + i] * matrixH[k, j]); + } + + a[(j * order) + i] = z; + } + } + } + + /// + /// Complex scalar division X/Y. + /// + /// Real part of X + /// Imaginary part of X + /// Real part of Y + /// Imaginary part of Y + /// Division result as a number. + private static Complex32 Cdiv(float xreal, float ximag, float yreal, float yimag) + { + if (Math.Abs(yimag) < Math.Abs(yreal)) + { + return new Complex32((xreal + (ximag * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal))), (ximag - (xreal * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal)))); + } + + return new Complex32((ximag + (xreal * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag))), (-xreal + (ximag * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag)))); + } + + /// + /// 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"); + } + + // 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 (VectorEv.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VectorEv.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = VectorEv.Count; + var tmp = new float[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + float value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i] * input.At(i, k); + } + + value /= (float)VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + float value = 0; + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j, k] = value; + } + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD 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 m matrix + // Check that b is a column vector with m entries + if (VectorEv.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VectorEv.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VT * b; + var order = VectorEv.Count; + var tmp = new float[order]; + float value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(j * order) + i] * input[i]; + } + + value /= (float)VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + value = 0; + for (int i = 0; i < order; i++) + { + value += ((DenseMatrix)MatrixEv).Data[(i * order) + j] * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); + } + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override float MultiplyT(float val1, float val2) + { + return val1 * val2; + } + } +} \ No newline at end of file diff --git a/src/Numerics/LinearAlgebra/Single/Factorization/DenseGramSchmidt.cs b/src/Numerics/LinearAlgebra/Single/Factorization/DenseGramSchmidt.cs new file mode 100644 index 00000000..fbe127ac --- /dev/null +++ b/src/Numerics/LinearAlgebra/Single/Factorization/DenseGramSchmidt.cs @@ -0,0 +1,238 @@ +// +// 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.Single.Factorization +{ + using System; + using Generic; + using Generic.Factorization; + using Properties; + using Threading; + + /// + /// A class which encapsulates the functionality of the QR decomposition Modified Gram-Schmidt Orthogonalization. + /// Any real square matrix A may be decomposed as A = QR where Q is an orthogonal mxn matrix and R is an nxn upper triangular matrix. + /// + /// + /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. + /// + public class DenseGramSchmidt : GramSchmidt + { + /// + /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// using the modified Gram-Schmidt method. + /// + /// The matrix to factor. + /// If is null. + /// If row count is less then column count + /// If is rank deficient + public DenseGramSchmidt(DenseMatrix 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); + Factorize(((DenseMatrix)MatrixQ).Data, MatrixQ.RowCount, MatrixQ.ColumnCount, ((DenseMatrix)MatrixR).Data); + } + + /// + /// Factorize matrix using the modified Gram-Schmidt method. + /// + /// Initial matrix. On exit is replaced by Q. + /// Number of rows in Q. + /// Number of columns in Q. + /// On exit is filled by R. + private static void Factorize(float[] q, int rowsQ, int columnsQ, float[] r) + { + for (var k = 0; k < columnsQ; k++) + { + var norm = 0.0f; + for (var i = 0; i < rowsQ; i++) + { + norm += q[(k * rowsQ) + i] * q[(k * rowsQ) + i]; + } + + norm = (float)Math.Sqrt(norm); + if (norm == 0.0) + { + throw new ArgumentException(Resources.ArgumentMatrixNotRankDeficient); + } + + r[(k * columnsQ) + k] = norm; + for (var i = 0; i < rowsQ; i++) + { + q[(k * rowsQ) + i] /= norm; + } + + for (var j = k + 1; j < columnsQ; j++) + { + int k1 = k; + int j1 = j; + var dot = CommonParallel.Aggregate(0, rowsQ, index => q[(k1 * rowsQ) + index] * q[(j1 * rowsQ) + index]); + r[(j * columnsQ) + k] = dot; + for (var i = 0; i < rowsQ; i++) + { + var value = q[(j * rowsQ) + i] - (q[(k * rowsQ) + i] * dot); + q[(j * rowsQ) + i] = value; + } + } + } + } + + /// + /// 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 dinput = input as DenseMatrix; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + var dresult = result as DenseMatrix; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense matrices at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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 (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 dinput = input as DenseVector; + if (dinput == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + var dresult = result as DenseVector; + if (dresult == null) + { + throw new NotImplementedException("Can only do GramSchmidt factorization for dense vectors at the moment."); + } + + Control.LinearAlgebraProvider.QRSolveFactored(((DenseMatrix)MatrixQ).Data, ((DenseMatrix)MatrixR).Data, MatrixQ.RowCount, MatrixQ.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 float MultiplyT(float val1, float 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(float val1) + { + return Math.Abs(val1); + } + + #endregion + } +} diff --git a/src/Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs index 23407184..8f9aef8a 100644 --- a/src/Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs +++ b/src/Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs @@ -110,15 +110,15 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization for (var i = 0; i < order; i++) { - MatrixD[i, i] = d[i]; + MatrixD.At(i, i, d[i]); if (e[i] > 0) { - MatrixD[i, i + 1] = e[i]; + MatrixD.At(i, i + 1, e[i]); } else if (e[i] < 0) { - MatrixD[i, i - 1] = e[i]; + MatrixD.At(i, i - 1, e[i]); } } @@ -157,9 +157,9 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization e[i] = d[i - 1]; for (var j = 0; j < i; j++) { - d[j] = MatrixEv[i - 1, j]; - MatrixEv[i, j] = 0.0f; - MatrixEv[j, i] = 0.0f; + d[j] = MatrixEv.At(i - 1, j); + MatrixEv.At(i, j, 0.0f); + MatrixEv.At(j, i, 0.0f); } } else @@ -191,13 +191,13 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization for (var j = 0; j < i; j++) { f = d[j]; - MatrixEv[j, i] = f; - g = e[j] + (MatrixEv[j, j] * f); + MatrixEv.At(j, i, f); + g = e[j] + (MatrixEv.At(j, j) * f); for (var k = j + 1; k <= i - 1; k++) { - g += MatrixEv[k, j] * d[k]; - e[k] += MatrixEv[k, j] * f; + g += MatrixEv.At(k, j) * d[k]; + e[k] += MatrixEv.At(k, j) * f; } e[j] = g; @@ -225,11 +225,11 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization for (var k = j; k <= i - 1; k++) { - MatrixEv[k, j] -= (f * e[k]) + (g * d[k]); + MatrixEv.At(k, j, MatrixEv.At(k, j) - (f * e[k]) - (g * d[k])); } - d[j] = MatrixEv[i - 1, j]; - MatrixEv[i, j] = 0.0f; + d[j] = MatrixEv.At(i - 1, j); + MatrixEv.At(i, j, 0.0f); } } @@ -239,14 +239,14 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization // Accumulate transformations. for (var i = 0; i < order - 1; i++) { - MatrixEv[order - 1, i] = MatrixEv[i, i]; - MatrixEv[i, i] = 1.0f; + MatrixEv.At(order - 1, i, MatrixEv.At(i, i)); + MatrixEv.At(i, i, 1.0f); var h = d[i + 1]; if (h != 0.0f) { for (var k = 0; k <= i; k++) { - d[k] = MatrixEv[k, i + 1] / h; + d[k] = MatrixEv.At(k, i + 1) / h; } for (var j = 0; j <= i; j++) @@ -254,29 +254,29 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization var g = 0.0f; for (var k = 0; k <= i; k++) { - g += MatrixEv[k, i + 1] * MatrixEv[k, j]; + g += MatrixEv.At(k, i + 1) * MatrixEv.At(k, j); } for (var k = 0; k <= i; k++) { - MatrixEv[k, j] -= g * d[k]; + MatrixEv.At(k, j, MatrixEv.At(k, j) - g * d[k]); } } } for (var k = 0; k <= i; k++) { - MatrixEv[k, i + 1] = 0.0f; + MatrixEv.At(k, i + 1, 0.0f); } } for (var j = 0; j < order; j++) { - d[j] = MatrixEv[order - 1, j]; - MatrixEv[order - 1, j] = 0.0f; + d[j] = MatrixEv.At(order - 1, j); + MatrixEv.At(order - 1, j, 0.0f); } - MatrixEv[order - 1, order - 1] = 1.0f; + MatrixEv.At(order - 1, order - 1, 1.0f); e[0] = 0.0f; } @@ -374,9 +374,9 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization // Accumulate transformation. for (var k = 0; k < order; k++) { - h = MatrixEv[k, i + 1]; - MatrixEv[k, i + 1] = (s * MatrixEv[k, i]) + (c * h); - MatrixEv[k, i] = (c * MatrixEv[k, i]) - (s * h); + h = MatrixEv.At(k, i + 1); + MatrixEv.At(k, i + 1, (s * MatrixEv.At(k, i)) + (c * h)); + MatrixEv.At(k, i, (c * MatrixEv.At(k, i)) - (s * h)); } } @@ -418,9 +418,9 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization d[i] = p; for (var j = 0; j < order; j++) { - p = MatrixEv[j, i]; - MatrixEv[j, i] = MatrixEv[j, k]; - MatrixEv[j, k] = p; + p = MatrixEv.At(j, i); + MatrixEv.At(j, i, MatrixEv.At(j, k)); + MatrixEv.At(j, k, p); } } } @@ -509,7 +509,7 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization { for (var j = 0; j < order; j++) { - MatrixEv[i, j] = i == j ? 1.0f : 0.0f; + MatrixEv.At(i, j, i == j ? 1.0f : 0.0f); } } @@ -527,14 +527,14 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization var g = 0.0f; for (var i = m; i < order; i++) { - g += ort[i] * MatrixEv[i, j]; + g += ort[i] * MatrixEv.At(i, j); } // Double division avoids possible underflow g = (g / ort[m]) / matrixH[m, m - 1]; for (var i = m; i < order; i++) { - MatrixEv[i, j] += g * ort[i]; + MatrixEv.At(i, j, MatrixEv.At(i, j) + g * ort[i]); } } } @@ -664,9 +664,9 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization // Accumulate transformations for (var i = 0; i < order; i++) { - z = MatrixEv[i, n - 1]; - MatrixEv[i, n - 1] = (q * z) + (p * MatrixEv[i, n]); - MatrixEv[i, n] = (q * MatrixEv[i, n]) - (p * z); + z = MatrixEv.At(i, n - 1); + MatrixEv.At(i, n - 1, (q * z) + (p * MatrixEv.At(i, n))); + MatrixEv.At(i, n, (q * MatrixEv.At(i, n)) - (p * z)); } // Complex pair @@ -854,16 +854,16 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization // Accumulate transformations for (var i = 0; i < order; i++) { - p = (x * MatrixEv[i, k]) + (y * MatrixEv[i, k + 1]); + p = (x * MatrixEv.At(i, k)) + (y * MatrixEv.At(i, k + 1)); if (notlast) { - p = p + (z * MatrixEv[i, k + 2]); - MatrixEv[i, k + 2] = MatrixEv[i, k + 2] - (p * r); + p = p + (z * MatrixEv.At(i, k + 2)); + MatrixEv.At(i, k + 2, MatrixEv.At(i, k + 2) - (p * r)); } - MatrixEv[i, k] = MatrixEv[i, k] - p; - MatrixEv[i, k + 1] = MatrixEv[i, k + 1] - (p * q); + MatrixEv.At(i, k, MatrixEv.At(i, k) - p); + MatrixEv.At(i, k + 1, MatrixEv.At(i, k + 1) - (p * q)); } } // (s != 0) } // k loop @@ -1047,10 +1047,10 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization z = 0.0f; for (var k = 0; k <= j; k++) { - z = z + (MatrixEv[i, k] * matrixH[k, j]); + z = z + (MatrixEv.At(i, k) * matrixH[k, j]); } - MatrixEv[i, j] = z; + MatrixEv.At(i, j, z); } } } diff --git a/src/Numerics/LinearAlgebra/Single/Factorization/GramSchmidt.cs b/src/Numerics/LinearAlgebra/Single/Factorization/UserGramSchmidt.cs similarity index 87% rename from src/Numerics/LinearAlgebra/Single/Factorization/GramSchmidt.cs rename to src/Numerics/LinearAlgebra/Single/Factorization/UserGramSchmidt.cs index a84c7362..44a20708 100644 --- a/src/Numerics/LinearAlgebra/Single/Factorization/GramSchmidt.cs +++ b/src/Numerics/LinearAlgebra/Single/Factorization/UserGramSchmidt.cs @@ -1,4 +1,4 @@ -// +// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics @@ -42,17 +42,17 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization /// /// The computation of the QR decomposition is done at construction time by modified Gram-Schmidt Orthogonalization. /// - public class GramSchmidt : QR + public class UserGramSchmidt : GramSchmidt { /// - /// Initializes a new instance of the class. This object creates an orthogonal matrix + /// Initializes a new instance of the class. This object creates an orthogonal matrix /// using the modified Gram-Schmidt method. /// /// The matrix to factor. /// If is null. /// If row count is less then column count /// If is rank deficient - public GramSchmidt(Matrix matrix) + public UserGramSchmidt(Matrix matrix) { if (matrix == null) { @@ -94,44 +94,6 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Factorization } } - /// - /// Gets a value indicating whether the matrix is full rank or not. - /// - /// true if the matrix is full rank; otherwise false. - public override bool IsFullRank - { - get - { - return true; - } - } - - /// - /// Gets the determinant of the matrix for which the QR matrix was computed. - /// - public override double Determinant - { - get - { - if (MatrixQ.RowCount != MatrixQ.ColumnCount) - { - throw new ArgumentException(Resources.ArgumentMatrixSquare); - } - - var det = 1.0; - for (var i = 0; i < MatrixR.ColumnCount; i++) - { - det *= MatrixR.At(i, i); - if (Math.Abs(MatrixR.At(i, i)).AlmostEqualInDecimalPlaces(0.0f, 7)) - { - return 0; - } - } - - return Math.Abs(det); - } - } - /// /// Solves a system of linear equations, AX = B, with A QR factorized. /// diff --git a/src/Numerics/LinearAlgebra/Single/Solvers/Iterative/MlkBiCgStab.cs b/src/Numerics/LinearAlgebra/Single/Solvers/Iterative/MlkBiCgStab.cs index 0e3c452a..2b959c28 100644 --- a/src/Numerics/LinearAlgebra/Single/Solvers/Iterative/MlkBiCgStab.cs +++ b/src/Numerics/LinearAlgebra/Single/Solvers/Iterative/MlkBiCgStab.cs @@ -34,8 +34,8 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Solvers.Iterative using System.Collections.Generic; using System.Diagnostics; using Distributions; - using Factorization; using Generic; + using Generic.Factorization; using Generic.Solvers; using Generic.Solvers.Preconditioners; using Generic.Solvers.Status; @@ -638,7 +638,7 @@ namespace MathNet.Numerics.LinearAlgebra.Single.Solvers.Iterative } // Compute the orthogonalization. - var gs = new GramSchmidt(matrix); + var gs = matrix.GramSchmidt(); var orthogonalMatrix = gs.Q; // Now transfer this to vectors diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 16cc424a..a2f4349a 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -119,6 +119,7 @@ + @@ -129,11 +130,13 @@ + + - + @@ -159,11 +162,13 @@ + + - + @@ -189,16 +194,21 @@ + + + + + - + @@ -233,7 +243,7 @@ - + diff --git a/src/Numerics/SpecialFunctions/Stability.cs b/src/Numerics/SpecialFunctions/Stability.cs index 984fe9a1..27e137df 100644 --- a/src/Numerics/SpecialFunctions/Stability.cs +++ b/src/Numerics/SpecialFunctions/Stability.cs @@ -31,6 +31,7 @@ namespace MathNet.Numerics { using System; + using System.Numerics; public partial class SpecialFunctions { @@ -66,6 +67,54 @@ namespace MathNet.Numerics ); } + /// + /// Numerically stable hypotenuse of a right angle triangle, i.e. (a,b) -> sqrt(a^2 + b^2) + /// + /// The length of side a of the triangle. + /// The length of side b of the triangle. + /// Returns sqrt(a2 + b2) without underflow/overflow. + public static Complex Hypotenuse(Complex a, Complex b) + { + if (a.Magnitude > b.Magnitude) + { + var r = b.Magnitude / a.Magnitude; + return a.Magnitude * Math.Sqrt(1 + (r * r)); + } + + if (b != 0.0) + { + // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm) + var r = a.Magnitude / b.Magnitude; + return b.Magnitude * Math.Sqrt(1 + (r * r)); + } + + return 0d; + } + + /// + /// Numerically stable hypotenuse of a right angle triangle, i.e. (a,b) -> sqrt(a^2 + b^2) + /// + /// The length of side a of the triangle. + /// The length of side b of the triangle. + /// Returns sqrt(a2 + b2) without underflow/overflow. + public static Complex32 Hypotenuse(Complex32 a, Complex32 b) + { + if (a.Magnitude > b.Magnitude) + { + var r = b.Magnitude / a.Magnitude; + return a.Magnitude * (float)Math.Sqrt(1 + (r * r)); + } + + if (b != 0.0f) + { + // NOTE (ruegg): not "!b.AlmostZero()" to avoid convergence issues (e.g. in SVD algorithm) + var r = a.Magnitude / b.Magnitude; + return b.Magnitude * (float)Math.Sqrt(1 + (r * r)); + } + + return 0f; + } + /// /// Numerically stable hypotenuse of a right angle triangle, i.e. (a,b) -> sqrt(a^2 + b^2) /// diff --git a/src/Silverlight/Silverlight.csproj b/src/Silverlight/Silverlight.csproj index f6a2c9a7..405a546a 100644 --- a/src/Silverlight/Silverlight.csproj +++ b/src/Silverlight/Silverlight.csproj @@ -284,6 +284,12 @@ LinearAlgebra\Complex32\Factorization\DenseCholesky.cs + + LinearAlgebra\Complex32\Factorization\DenseEvd.cs + + + LinearAlgebra\Complex32\Factorization\DenseGramSchmidt.cs + LinearAlgebra\Complex32\Factorization\DenseLU.cs @@ -293,15 +299,15 @@ LinearAlgebra\Complex32\Factorization\DenseSvd.cs - - LinearAlgebra\Complex32\Factorization\GramSchmidt.cs - LinearAlgebra\Complex32\Factorization\UserCholesky.cs LinearAlgebra\Complex32\Factorization\UserEvd.cs + + LinearAlgebra\Complex32\Factorization\UserGramSchmidt.cs + LinearAlgebra\Complex32\Factorization\UserLU.cs @@ -374,6 +380,12 @@ LinearAlgebra\Complex\Factorization\DenseCholesky.cs + + LinearAlgebra\Complex\Factorization\DenseEvd.cs + + + LinearAlgebra\Complex\Factorization\DenseGramSchmidt.cs + LinearAlgebra\Complex\Factorization\DenseLU.cs @@ -383,15 +395,15 @@ LinearAlgebra\Complex\Factorization\DenseSvd.cs - - LinearAlgebra\Complex\Factorization\GramSchmidt.cs - LinearAlgebra\Complex\Factorization\UserCholesky.cs LinearAlgebra\Complex\Factorization\UserEvd.cs + + LinearAlgebra\Complex\Factorization\UserGramSchmidt.cs + LinearAlgebra\Complex\Factorization\UserLU.cs @@ -464,6 +476,12 @@ LinearAlgebra\Double\Factorization\DenseCholesky.cs + + LinearAlgebra\Double\Factorization\DenseEvd.cs + + + LinearAlgebra\Double\Factorization\DenseGramSchmidt.cs + LinearAlgebra\Double\Factorization\DenseLU.cs @@ -473,9 +491,6 @@ LinearAlgebra\Double\Factorization\DenseSvd.cs - - LinearAlgebra\Double\Factorization\GramSchmidt.cs - LinearAlgebra\Double\Factorization\SparseCholesky.cs @@ -494,6 +509,9 @@ LinearAlgebra\Double\Factorization\UserEvd.cs + + LinearAlgebra\Double\Factorization\UserGramSchmidt.cs + LinearAlgebra\Double\Factorization\UserLU.cs @@ -569,6 +587,9 @@ LinearAlgebra\Generic\Factorization\Evd.cs + + LinearAlgebra\Generic\Factorization\GramSchmidt.cs + LinearAlgebra\Single\DenseMatrix.cs @@ -581,6 +602,12 @@ LinearAlgebra\Single\Factorization\DenseCholesky.cs + + LinearAlgebra\Single\Factorization\DenseEvd.cs + + + LinearAlgebra\Single\Factorization\DenseGramSchmidt.cs + LinearAlgebra\Single\Factorization\DenseLU.cs @@ -590,15 +617,15 @@ LinearAlgebra\Single\Factorization\DenseSvd.cs - - LinearAlgebra\Single\Factorization\GramSchmidt.cs - LinearAlgebra\Single\Factorization\UserCholesky.cs LinearAlgebra\Single\Factorization\UserEvd.cs + + LinearAlgebra\Single\Factorization\UserGramSchmidt.cs + LinearAlgebra\Single\Factorization\UserLU.cs diff --git a/src/UnitTests/DistributionTests/Discrete/PoissonTests.cs b/src/UnitTests/DistributionTests/Discrete/PoissonTests.cs new file mode 100644 index 00000000..93058ad3 --- /dev/null +++ b/src/UnitTests/DistributionTests/Discrete/PoissonTests.cs @@ -0,0 +1,219 @@ +// +// 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.DistributionTests.Discrete +{ + using System; + using System.Linq; + using MbUnit.Framework; + using Distributions; + + [TestFixture] + public class PoissonTests + { + [SetUp] + public void SetUp() + { + Control.CheckDistributionParameters = true; + } + + [Test] + [Row(1.5)] + [Row(5.4)] + [Row(10.8)] + public void CanCreatePoisson(double lambda) + { + var d = new Poisson(lambda); + Assert.AreEqual(lambda, d.Lambda); + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Row(Double.NaN)] + [Row(-1.5)] + [Row(0.0)] + public void PoissonCreateFailsWithBadParameters(double lambda) + { + var d = new Poisson(lambda); + } + + [Test] + public void ValidateToString() + { + var d = new Poisson(0.3); + Assert.AreEqual(String.Format("Poisson(λ = {0})", 0.3), d.ToString()); + } + + [Test] + [Row(1.5)] + [Row(5.4)] + [Row(10.8)] + public void CanSetProbabilityOfOne(double lambda) + { + var d = new Poisson(0.3) + { + Lambda = lambda + }; + } + + [Test] + [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Row(Double.NaN)] + [Row(-1.5)] + [Row(0.0)] + public void SetProbabilityOfOneFails(double lambda) + { + var d = new Poisson(0.3) + { + Lambda = lambda + }; + } + + [Test] + [Row(1.5)] + [Row(5.4)] + [Row(10.8)] + public void ValidateEntropy(double lambda) + { + var d = new Poisson(lambda); + Assert.AreEqual((0.5 * Math.Log(2 * Math.PI * Math.E * lambda)) - (1.0 / (12.0 * lambda)) - 1.0 / (24.0 * lambda * lambda) - (19.0 / (360.0 * lambda * lambda * lambda)), d.Entropy); + } + + [Test] + [Row(1.5)] + [Row(5.4)] + [Row(10.8)] + public void ValidateSkewness(double lambda) + { + var d = new Poisson(lambda); + Assert.AreEqual(1.0 / Math.Sqrt(lambda), d.Skewness); + } + + [Test] + [Row(1.5)] + [Row(5.4)] + [Row(10.8)] + public void ValidateMode(double lambda) + { + var d = new Poisson(lambda); + Assert.AreEqual((int)Math.Floor(lambda), d.Mode); + } + + [Test] + [Row(1.5)] + [Row(5.4)] + [Row(10.8)] + public void ValidateMedian(double lambda) + { + var d = new Poisson(lambda); + Assert.AreEqual((int)Math.Floor(lambda + (1.0 / 3.0) - (0.02 / lambda)), d.Median); + } + + [Test] + public void ValidateMinimum() + { + var d = new Poisson(0.3); + Assert.AreEqual(0, d.Minimum); + } + + [Test] + public void ValidateMaximum() + { + var d = new Poisson(0.3); + Assert.AreEqual(int.MaxValue, d.Maximum); + } + + [Test] + [Row(1.5, 1, 0.334695240222645000000000000000)] + [Row(1.5, 10, 0.000003545747740570180000000000)] + [Row(1.5, 20, 0.000000000000000304971208961018)] + [Row(5.4, 1, 0.024389537090108400000000000000)] + [Row(5.4, 10, 0.026241240591792300000000000000)] + [Row(5.4, 20, 0.000000825202200316548000000000)] + [Row(10.8, 1, 0.000220314636840657000000000000)] + [Row(10.8, 10, 0.121365183659420000000000000000)] + [Row(10.8, 20, 0.003908139778574110000000000000)] + public void ValidateProbability(double lambda, int x, double result) + { + var d = new Poisson(lambda); + Assert.AreApproximatelyEqual(d.Probability(x), result, 1e-12); + } + + [Test] + [Row(1.5, 1, 0.334695240222645000000000000000)] + [Row(1.5, 10, 0.000003545747740570180000000000)] + [Row(1.5, 20, 0.000000000000000304971208961018)] + [Row(5.4, 1, 0.024389537090108400000000000000)] + [Row(5.4, 10, 0.026241240591792300000000000000)] + [Row(5.4, 20, 0.000000825202200316548000000000)] + [Row(10.8, 1, 0.000220314636840657000000000000)] + [Row(10.8, 10, 0.121365183659420000000000000000)] + [Row(10.8, 20, 0.003908139778574110000000000000)] + public void ValidateProbabilityLn(double lambda, int x, double result) + { + var d = new Poisson(lambda); + Assert.AreApproximatelyEqual(d.ProbabilityLn(x), Math.Log(result), 1e-12); + } + + [Test] + public void CanSample() + { + var d = new Poisson(0.3); + var s = d.Sample(); + } + + [Test] + public void CanSampleSequence() + { + var d = new Poisson(0.3); + var ied = d.Samples(); + var e = ied.Take(5).ToArray(); + } + + [Test] + [Row(1.5, 1, 0.5578254003710750000000)] + [Row(1.5, 10, 0.9999994482467640000000)] + [Row(1.5, 20, 1.0000000000000000000000)] + [Row(5.4, 1, 0.0289061180327211000000)] + [Row(5.4, 10, 0.9774863006897650000000)] + [Row(5.4, 20, 0.9999997199928290000000)] + [Row(10.8, 1, 0.0002407141402518290000)] + [Row(10.8, 10, 0.4839692359955690000000)] + [Row(10.8, 20, 0.9961800769608090000000)] + [Row(20.1, 1, 0.0000000393516882521484)] + [Row(20.1, 10, 0.0102444128791257000000)] + [Row(20.1, 20, 0.5502097908860160000000)] + public void ValidateCumulativeDistribution(double lambda, int x, double result) + { + var d = new Poisson(lambda); + Assert.AreApproximatelyEqual(d.CumulativeDistribution(x), result, 1e-12); + } + } +} \ No newline at end of file diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/EvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/EvdTests.cs new file mode 100644 index 00000000..cba81eae --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/EvdTests.cs @@ -0,0 +1,364 @@ +// +// 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.Complex; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex.Factorization; + + public class EvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new DenseEvd(null); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + + Assert.AreEqual(I.RowCount, factorEvd.EVectors().RowCount); + Assert.AreEqual(I.RowCount, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(I.ColumnCount, factorEvd.D().RowCount); + Assert.AreEqual(I.ColumnCount, factorEvd.D().ColumnCount); + + for (var i = 0; i < factorEvd.EValues().Count; i++) + { + Assert.AreEqual(Complex.One, factorEvd.EValues()[i]); + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A*V = λ*V + var matrixAv = matrixA * factorEvd.EVectors(); + var matrixLv = factorEvd.EVectors() * factorEvd.D(); + + for (var i = 0; i < matrixAv.RowCount; i++) + { + for (var j = 0; j < matrixAv.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixAv[i, j].Real, matrixLv[i, j].Real, 1e-9); + Assert.AreApproximatelyEqual(matrixAv[i, j].Imaginary, matrixLv[i, j].Imaginary, 1e-9); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A = V*λ*VT + var matrix = factorEvd.EVectors() * factorEvd.D() * factorEvd.EVectors().ConjugateTranspose(); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrix[i, j].Real, matrixA[i, j].Real, 1e-9); + Assert.AreApproximatelyEqual(matrix[i, j].Imaginary, matrixA[i, j].Imaginary, 1e-9); + } + } + } + + [Test] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CheckRankSquare(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Rank, order); + } + + + [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 factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Determinant, 0); + Assert.AreEqual(factorEvd.Rank, order - 1); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + Assert.AreEqual(1.0, factorEvd.Determinant); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVectorAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var resultx = factorEvd.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, 1e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-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 CanSolveForRandomMatrixAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixX = factorEvd.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, 1e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1e-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 CanSolveForRandomVectorAndSymmetricMatrixWhenResultVectorGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new DenseVector(order); + factorEvd.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, 1e-9); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-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(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixAndSymmetricMatrixWhenResultMatrixGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new DenseMatrix(order, order); + factorEvd.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, 1e-9); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1e-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 index 5ef1872b..783ad427 100644 --- a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/GramSchmidtTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/GramSchmidtTests.cs @@ -30,6 +30,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization { + using LinearAlgebra.Complex; using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Complex.Factorization; @@ -40,14 +41,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [ExpectedArgumentNullException] public void ConstructorNull() { - new GramSchmidt(null); + new DenseGramSchmidt(null); } [Test] [ExpectedArgumentException] public void WideMatrixThrowsInvalidMatrixOperationException() { - new GramSchmidt(new UserDefinedMatrix(3, 4)); + new DenseGramSchmidt(new DenseMatrix(3, 4)); } [Test] @@ -56,7 +57,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [Row(100)] public void CanFactorizeIdentity(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(I.RowCount, factorGramSchmidt.Q.RowCount); @@ -100,7 +101,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [Row(100)] public void IdentityDeterminantIsOne(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(1.0, factorGramSchmidt.Determinant); } @@ -115,7 +116,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [MultipleAsserts] public void CanFactorizeRandomMatrix(int row, int column) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); var factorGramSchmidt = matrixA.GramSchmidt(); // Make sure the Q has the right dimensions. @@ -180,11 +181,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [MultipleAsserts] public void CanSolveForRandomVector(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var resultx = factorGramSchmidt.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -218,11 +219,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [MultipleAsserts] public void CanSolveForRandomMatrix(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixX = factorGramSchmidt.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A @@ -262,12 +263,12 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [MultipleAsserts] public void CanSolveForRandomVectorWhenResultVectorGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var vectorbCopy = vectorb.Clone(); - var resultx = new UserDefinedVector(order); + var resultx = new DenseVector(order); factorGramSchmidt.Solve(vectorb,resultx); Assert.AreEqual(vectorb.Count, resultx.Count); @@ -307,14 +308,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization [MultipleAsserts] public void CanSolveForRandomMatrixWhenResultMatrixGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixBCopy = matrixB.Clone(); - var matrixX = new UserDefinedMatrix(order, order); + var matrixX = new DenseMatrix(order, order); factorGramSchmidt.Solve(matrixB,matrixX); // The solution X row dimension is equal to the column dimension of A diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserEvdTests.cs index b7012729..7d2b7bc2 100644 --- a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserEvdTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserEvdTests.cs @@ -191,10 +191,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); - var resultx = factorSvd.Solve(vectorb); + var resultx = factorEvd.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -229,10 +229,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); - var matrixX = factorSvd.Solve(matrixB); + var matrixX = factorEvd.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); @@ -273,11 +273,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); var vectorbCopy = vectorb.Clone(); var resultx = new UserDefinedVector(order); - factorSvd.Solve(vectorb, resultx); + factorEvd.Solve(vectorb, resultx); var bReconstruct = matrixA * resultx; @@ -316,13 +316,13 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); var matrixBCopy = matrixB.Clone(); var matrixX = new UserDefinedMatrix(order, order); - factorSvd.Solve(matrixB, matrixX); + factorEvd.Solve(matrixB, matrixX); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserGramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserGramSchmidtTests.cs new file mode 100644 index 00000000..9eb64a57 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserGramSchmidtTests.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 UserGramSchmidtTests + { + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserGramSchmidt(null); + } + + [Test] + [ExpectedArgumentException] + public void WideMatrixThrowsInvalidMatrixOperationException() + { + new UserGramSchmidt(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/Complex32/Factorization/EvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/EvdTests.cs new file mode 100644 index 00000000..9adede82 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/EvdTests.cs @@ -0,0 +1,365 @@ +// +// 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.Complex32.Factorization +{ + using System.Numerics; + using LinearAlgebra.Complex32; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex.Factorization; + + public class EvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new DenseEvd(null); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + + Assert.AreEqual(I.RowCount, factorEvd.EVectors().RowCount); + Assert.AreEqual(I.RowCount, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(I.ColumnCount, factorEvd.D().RowCount); + Assert.AreEqual(I.ColumnCount, factorEvd.D().ColumnCount); + + for (var i = 0; i < factorEvd.EValues().Count; i++) + { + Assert.AreEqual(Complex.One, factorEvd.EValues()[i]); + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A*V = λ*V + var matrixAv = matrixA * factorEvd.EVectors(); + var matrixLv = factorEvd.EVectors() * factorEvd.D(); + + for (var i = 0; i < matrixAv.RowCount; i++) + { + for (var j = 0; j < matrixAv.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixAv[i, j].Real, matrixLv[i, j].Real, 1e-4f); + Assert.AreApproximatelyEqual(matrixAv[i, j].Imaginary, matrixLv[i, j].Imaginary, 1e-4f); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + [Ignore] + public void CanFactorizeRandomSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A = V*λ*VT + var matrix = factorEvd.EVectors() * factorEvd.D() * factorEvd.EVectors().ConjugateTranspose(); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrix[i, j].Real, matrixA[i, j].Real, 1e-3f); + Assert.AreApproximatelyEqual(matrix[i, j].Imaginary, matrixA[i, j].Imaginary, 1e-3f); + } + } + } + + [Test] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CheckRankSquare(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Rank, order); + } + + + [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 factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Determinant, 0); + Assert.AreEqual(factorEvd.Rank, order - 1); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + Assert.AreEqual(1.0, factorEvd.Determinant); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + //[Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVectorAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var resultx = factorEvd.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, 1e-3f); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-3f); + } + + // 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 CanSolveForRandomMatrixAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixX = factorEvd.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, 1e-2f); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1e-2f); + } + } + + // 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 CanSolveForRandomVectorAndSymmetricMatrixWhenResultVectorGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new DenseVector(order); + factorEvd.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, 1e-3f); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-3f); + } + + // 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(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixAndSymmetricMatrixWhenResultMatrixGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new DenseMatrix(order, order); + factorEvd.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, 1e-2f); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1e-2f); + } + } + + // 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/Complex32/Factorization/GramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/GramSchmidtTests.cs index f2a7daec..0a4dd1ac 100644 --- a/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/GramSchmidtTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/GramSchmidtTests.cs @@ -30,6 +30,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization { + using LinearAlgebra.Complex32; using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Complex32.Factorization; @@ -40,14 +41,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [ExpectedArgumentNullException] public void ConstructorNull() { - new GramSchmidt(null); + new DenseGramSchmidt(null); } [Test] [ExpectedArgumentException] public void WideMatrixThrowsInvalidMatrixOperationException() { - new GramSchmidt(new UserDefinedMatrix(3, 4)); + new DenseGramSchmidt(new DenseMatrix(3, 4)); } [Test] @@ -56,7 +57,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [Row(100)] public void CanFactorizeIdentity(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(I.RowCount, factorGramSchmidt.Q.RowCount); @@ -100,7 +101,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [Row(100)] public void IdentityDeterminantIsOne(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(1.0f, factorGramSchmidt.Determinant); } @@ -115,7 +116,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [MultipleAsserts] public void CanFactorizeRandomMatrix(int row, int column) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); var factorGramSchmidt = matrixA.GramSchmidt(); // Make sure the Q has the right dimensions. @@ -180,11 +181,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [MultipleAsserts] public void CanSolveForRandomVector(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var resultx = factorGramSchmidt.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -218,11 +219,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [MultipleAsserts] public void CanSolveForRandomMatrix(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixX = factorGramSchmidt.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A @@ -262,12 +263,12 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [MultipleAsserts] public void CanSolveForRandomVectorWhenResultVectorGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var vectorbCopy = vectorb.Clone(); - var resultx = new UserDefinedVector(order); + var resultx = new DenseVector(order); factorGramSchmidt.Solve(vectorb,resultx); Assert.AreEqual(vectorb.Count, resultx.Count); @@ -307,14 +308,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [MultipleAsserts] public void CanSolveForRandomMatrixWhenResultMatrixGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixBCopy = matrixB.Clone(); - var matrixX = new UserDefinedMatrix(order, order); + var matrixX = new DenseMatrix(order, order); factorGramSchmidt.Solve(matrixB,matrixX); // The solution X row dimension is equal to the column dimension of A diff --git a/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserEvdTests.cs index 9a1493f3..35f561dd 100644 --- a/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserEvdTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserEvdTests.cs @@ -186,16 +186,16 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [Row(5)] [Row(10)] [Row(50)] - [Row(100)] + // [Row(100)] [MultipleAsserts] public void CanSolveForRandomVectorAndSymmetricMatrix(int order) { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); - var resultx = factorSvd.Solve(vectorb); + var resultx = factorEvd.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -230,10 +230,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); - var matrixX = factorSvd.Solve(matrixB); + var matrixX = factorEvd.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); @@ -268,17 +268,17 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization [Row(5)] [Row(10)] [Row(50)] - [Row(100)] + // [Row(100)] [MultipleAsserts] public void CanSolveForRandomVectorAndSymmetricMatrixWhenResultVectorGiven(int order) { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); var vectorbCopy = vectorb.Clone(); var resultx = new UserDefinedVector(order); - factorSvd.Solve(vectorb, resultx); + factorEvd.Solve(vectorb, resultx); var bReconstruct = matrixA * resultx; @@ -317,13 +317,13 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex32.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); var matrixBCopy = matrixB.Clone(); var matrixX = new UserDefinedMatrix(order, order); - factorSvd.Solve(matrixB, matrixX); + factorEvd.Solve(matrixB, matrixX); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); diff --git a/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserGramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserGramSchmidtTests.cs new file mode 100644 index 00000000..e570e764 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserGramSchmidtTests.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.Complex32.Factorization +{ + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex32.Factorization; + + public class UserGramSchmidtTests + { + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserGramSchmidt(null); + } + + [Test] + [ExpectedArgumentException] + public void WideMatrixThrowsInvalidMatrixOperationException() + { + new UserGramSchmidt(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.0f, factorGramSchmidt.R[i, j]); + } + else + { + Assert.AreEqual(0.0f, 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.0f, factorGramSchmidt.Q[i, j]); + } + else + { + Assert.AreEqual(0.0f, 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.0f, 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.0f, 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, 1e-3f); + Assert.AreApproximatelyEqual(matrixA[i, j].Imaginary, matrixQfromR[i, j].Imaginary, 1e-3f); + } + } + + // 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.0f, 1e-3f); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0f, 1e-3f); + } + else + { + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Real, 0.0f, 1e-3f); + Assert.AreApproximatelyEqual(matrixQсtQ[i, j].Imaginary, 0.0f, 1e-3f); + } + } + } + + } + + [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, 1e-3f); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-3f); + } + + // 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, 1e-3f); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1e-3f); + } + } + + // 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, 1e-3f); + Assert.AreApproximatelyEqual(vectorb[i].Imaginary, bReconstruct[i].Imaginary, 1e-3f); + } + + // 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, 1e-3f); + Assert.AreApproximatelyEqual(matrixB[i, j].Imaginary, matrixBReconstruct[i, j].Imaginary, 1e-3f); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure B didn't change. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreEqual(matrixBCopy[i, j], matrixB[i, j]); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/EvdTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/EvdTests.cs new file mode 100644 index 00000000..090433e2 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/EvdTests.cs @@ -0,0 +1,358 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization +{ + using System.Numerics; + using LinearAlgebra.Double; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Double.Factorization; + + public class EvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new DenseEvd(null); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + + Assert.AreEqual(I.RowCount, factorEvd.EVectors().RowCount); + Assert.AreEqual(I.RowCount, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(I.ColumnCount, factorEvd.D().RowCount); + Assert.AreEqual(I.ColumnCount, factorEvd.D().ColumnCount); + + for (var i = 0; i < factorEvd.EValues().Count; i++) + { + Assert.AreEqual(Complex.One, factorEvd.EValues()[i]); + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A*V = λ*V + var matrixAv = matrixA * factorEvd.EVectors(); + var matrixLv = factorEvd.EVectors() * factorEvd.D(); + + for (var i = 0; i < matrixAv.RowCount; i++) + { + for (var j = 0; j < matrixAv.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixAv[i, j], matrixLv[i, j], 1.0e-10); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A = V*λ*VT + var matrix = factorEvd.EVectors() * factorEvd.D() * factorEvd.EVectors().Transpose(); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrix[i, j], matrixA[i, j], 1.0e-10); + } + } + } + + [Test] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CheckRankSquare(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Rank, order); + } + + + [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 factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Determinant, 0); + Assert.AreEqual(factorEvd.Rank, order - 1); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + Assert.AreEqual(1.0, factorEvd.Determinant); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVectorAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var resultx = factorEvd.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], bReconstruct[i], 1.0e-10); + } + + // 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 CanSolveForRandomMatrixAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixX = factorEvd.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-10); + } + } + + // 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 CanSolveForRandomVectorAndSymmetricMatrixWhenResultVectorGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new DenseVector(order); + factorEvd.Solve(vectorb, resultx); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-10); + } + + // 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(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixAndSymmetricMatrixWhenResultMatrixGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new DenseMatrix(order, order); + factorEvd.Solve(matrixB, matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-10); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure B didn't change. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreEqual(matrixBCopy[i, j], matrixB[i, j]); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/GramSchmidtTests.cs index 87932471..81d5273e 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.Double; using LinearAlgebra.Generic.Factorization; using MbUnit.Framework; using LinearAlgebra.Double.Factorization; @@ -40,14 +41,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [ExpectedArgumentNullException] public void ConstructorNull() { - new GramSchmidt(null); + new DenseGramSchmidt(null); } [Test] [ExpectedArgumentException] public void WideMatrixThrowsInvalidMatrixOperationException() { - new GramSchmidt(new UserDefinedMatrix(3, 4)); + new DenseGramSchmidt(new DenseMatrix(3, 4)); } [Test] @@ -56,7 +57,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [Row(100)] public void CanFactorizeIdentity(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(I.RowCount, factorGramSchmidt.Q.RowCount); @@ -100,7 +101,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [Row(100)] public void IdentityDeterminantIsOne(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(1.0, factorGramSchmidt.Determinant); } @@ -115,7 +116,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [MultipleAsserts] public void CanFactorizeRandomMatrix(int row, int column) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); var factorGramSchmidt = matrixA.GramSchmidt(); // Make sure the Q has the right dimensions. @@ -159,11 +160,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [MultipleAsserts] public void CanSolveForRandomVector(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var resultx = factorGramSchmidt.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -196,11 +197,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [MultipleAsserts] public void CanSolveForRandomMatrix(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixX = factorGramSchmidt.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A @@ -239,12 +240,12 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [MultipleAsserts] public void CanSolveForRandomVectorWhenResultVectorGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var vectorbCopy = vectorb.Clone(); - var resultx = new UserDefinedVector(order); + var resultx = new DenseVector(order); factorGramSchmidt.Solve(vectorb,resultx); Assert.AreEqual(vectorb.Count, resultx.Count); @@ -283,14 +284,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization [MultipleAsserts] public void CanSolveForRandomMatrixWhenResultMatrixGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixBCopy = matrixB.Clone(); - var matrixX = new UserDefinedMatrix(order, order); + var matrixX = new DenseMatrix(order, order); factorGramSchmidt.Solve(matrixB,matrixX); // The solution X row dimension is equal to the column dimension of A diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserEvdTests.cs index 43f13acf..82b6686a 100644 --- a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserEvdTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserEvdTests.cs @@ -93,7 +93,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { for (var j = 0; j < matrixAv.ColumnCount; j++) { - Assert.AreApproximatelyEqual(matrixAv[i, j], matrixLv[i, j], 1.0e-11); + Assert.AreApproximatelyEqual(matrixAv[i, j], matrixLv[i, j], 1.0e-10); } } } @@ -124,7 +124,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { for (var j = 0; j < matrix.ColumnCount; j++) { - Assert.AreApproximatelyEqual(matrix[i, j], matrixA[i, j], 1.0e-11); + Assert.AreApproximatelyEqual(matrix[i, j], matrixA[i, j], 1.0e-10); } } } @@ -189,10 +189,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); - var resultx = factorSvd.Solve(vectorb); + var resultx = factorEvd.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -201,7 +201,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization // Check the reconstruction. for (var i = 0; i < vectorb.Count; i++) { - Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-11); + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-10); } // Make sure A didn't change. @@ -226,10 +226,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); - var matrixX = factorSvd.Solve(matrixB); + var matrixX = factorEvd.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); @@ -243,7 +243,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { for (var j = 0; j < matrixB.ColumnCount; j++) { - Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-11); + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-10); } } @@ -269,18 +269,18 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); var vectorbCopy = vectorb.Clone(); var resultx = new UserDefinedVector(order); - factorSvd.Solve(vectorb, resultx); + factorEvd.Solve(vectorb, resultx); var bReconstruct = matrixA * resultx; // Check the reconstruction. for (var i = 0; i < vectorb.Count; i++) { - Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-11); + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-10); } // Make sure A didn't change. @@ -311,13 +311,13 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); var matrixBCopy = matrixB.Clone(); var matrixX = new UserDefinedMatrix(order, order); - factorSvd.Solve(matrixB, matrixX); + factorEvd.Solve(matrixB, matrixX); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); @@ -331,7 +331,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization { for (var j = 0; j < matrixB.ColumnCount; j++) { - Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-11); + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-10); } } diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserGramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserGramSchmidtTests.cs new file mode 100644 index 00000000..b300b664 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserGramSchmidtTests.cs @@ -0,0 +1,331 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Double.Factorization +{ + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Double.Factorization; + + public class UserGramSchmidtTests + { + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserGramSchmidt(null); + } + + [Test] + [ExpectedArgumentException] + public void WideMatrixThrowsInvalidMatrixOperationException() + { + new UserGramSchmidt(new UserDefinedMatrix(3, 4)); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorGramSchmidt = I.GramSchmidt(); + + Assert.AreEqual(I.RowCount, factorGramSchmidt.Q.RowCount); + Assert.AreEqual(I.ColumnCount, factorGramSchmidt.Q.ColumnCount); + + for (var i = 0; i < factorGramSchmidt.R.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.R.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(1.0, factorGramSchmidt.R[i, j]); + } + else + { + Assert.AreEqual(0.0, factorGramSchmidt.R[i, j]); + } + } + } + + for (var i = 0; i < factorGramSchmidt.Q.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.Q.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(1.0, factorGramSchmidt.Q[i, j]); + } + else + { + Assert.AreEqual(0.0, factorGramSchmidt.Q[i, j]); + } + } + } + } + + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorGramSchmidt = I.GramSchmidt(); + Assert.AreEqual(1.0, factorGramSchmidt.Determinant); + } + + [Test] + [Row(1,1)] + [Row(2,2)] + [Row(5,5)] + [Row(10,6)] + [Row(50,48)] + [Row(100,98)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var factorGramSchmidt = matrixA.GramSchmidt(); + + // Make sure the Q has the right dimensions. + Assert.AreEqual(row, factorGramSchmidt.Q.RowCount); + Assert.AreEqual(column, factorGramSchmidt.Q.ColumnCount); + + // Make sure the R has the right dimensions. + Assert.AreEqual(column, factorGramSchmidt.R.RowCount); + Assert.AreEqual(column, factorGramSchmidt.R.ColumnCount); + + // Make sure the R factor is upper triangular. + for (var i = 0; i < factorGramSchmidt.R.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.R.ColumnCount; j++) + { + if (i > j) + { + Assert.AreEqual(0.0, factorGramSchmidt.R[i, j]); + } + } + } + + // Make sure the Q*R is the original matrix. + var matrixQfromR = factorGramSchmidt.Q * factorGramSchmidt.R; + for (var i = 0; i < matrixQfromR.RowCount; i++) + { + for (var j = 0; j < matrixQfromR.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixA[i, j], matrixQfromR[i, j], 1.0e-11); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorGramSchmidt.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < order; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-11); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorGramSchmidt.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-11); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVectorWhenResultVectorGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorGramSchmidt.Solve(vectorb,resultx); + + Assert.AreEqual(vectorb.Count, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1.0e-11); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure b didn't change. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreEqual(vectorbCopy[i], vectorb[i]); + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixWhenResultMatrixGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorGramSchmidt.Solve(matrixB,matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1.0e-11); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure B didn't change. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreEqual(matrixBCopy[i, j], matrixB[i, j]); + } + } + } + } +} diff --git a/src/UnitTests/LinearAlgebraTests/Single/Factorization/EvdTests.cs b/src/UnitTests/LinearAlgebraTests/Single/Factorization/EvdTests.cs new file mode 100644 index 00000000..9d3fde4e --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Single/Factorization/EvdTests.cs @@ -0,0 +1,359 @@ +// +// 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.Single.Factorization +{ + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using LinearAlgebra.Single; + using MbUnit.Framework; + using LinearAlgebra.Single.Factorization; + + public class EvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new DenseEvd(null); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + + Assert.AreEqual(I.RowCount, factorEvd.EVectors().RowCount); + Assert.AreEqual(I.RowCount, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(I.ColumnCount, factorEvd.D().RowCount); + Assert.AreEqual(I.ColumnCount, factorEvd.D().ColumnCount); + + for (var i = 0; i < factorEvd.EValues().Count; i++) + { + Assert.AreEqual(Complex.One, factorEvd.EValues()[i]); + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A*V = λ*V + var matrixAv = matrixA * factorEvd.EVectors(); + var matrixLv = factorEvd.EVectors() * factorEvd.D(); + + for (var i = 0; i < matrixAv.RowCount; i++) + { + for (var j = 0; j < matrixAv.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixAv[i, j], matrixLv[i, j], 1e-3f); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [Ignore] + [MultipleAsserts] + public void CanFactorizeRandomSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(order, factorEvd.EVectors().RowCount); + Assert.AreEqual(order, factorEvd.EVectors().ColumnCount); + + Assert.AreEqual(order, factorEvd.D().RowCount); + Assert.AreEqual(order, factorEvd.D().ColumnCount); + + // Make sure the A = V*λ*VT + var matrix = factorEvd.EVectors() * factorEvd.D() * factorEvd.EVectors().Transpose(); + + for (var i = 0; i < matrix.RowCount; i++) + { + for (var j = 0; j < matrix.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrix[i, j], matrixA[i, j], 1e-3f); + } + } + } + + [Test] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CheckRankSquare(int order) + { + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Rank, order); + } + + + [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 factorEvd = matrixA.Evd(); + + Assert.AreEqual(factorEvd.Determinant, 0); + Assert.AreEqual(factorEvd.Rank, order - 1); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = DenseMatrix.Identity(order); + var factorEvd = I.Evd(); + Assert.AreEqual(1.0, factorEvd.Determinant); + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVectorAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var resultx = factorEvd.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], bReconstruct[i], 1e-3f); + } + + // 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 CanSolveForRandomMatrixAndSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixX = factorEvd.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1e-2f); + } + } + + // 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 CanSolveForRandomVectorAndSymmetricMatrixWhenResultVectorGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new DenseVector(order); + factorEvd.Solve(vectorb, resultx); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 1e-3f); + } + + // 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(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixAndSymmetricMatrixWhenResultMatrixGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteDenseMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorEvd = matrixA.Evd(); + + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new DenseMatrix(order, order); + factorEvd.Solve(matrixB, matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 1e-2f); + } + } + + // 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/Single/Factorization/GramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Single/Factorization/GramSchmidtTests.cs index 608f1954..04d394d4 100644 --- a/src/UnitTests/LinearAlgebraTests/Single/Factorization/GramSchmidtTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Single/Factorization/GramSchmidtTests.cs @@ -31,6 +31,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization { using LinearAlgebra.Generic.Factorization; + using LinearAlgebra.Single; using MbUnit.Framework; using LinearAlgebra.Single.Factorization; @@ -40,14 +41,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [ExpectedArgumentNullException] public void ConstructorNull() { - new GramSchmidt(null); + new DenseGramSchmidt(null); } [Test] [ExpectedArgumentException] public void WideMatrixThrowsInvalidMatrixOperationException() { - new GramSchmidt(new UserDefinedMatrix(3, 4)); + new DenseGramSchmidt(new DenseMatrix(3, 4)); } [Test] @@ -56,7 +57,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [Row(100)] public void CanFactorizeIdentity(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(I.RowCount, factorGramSchmidt.Q.RowCount); @@ -100,7 +101,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [Row(100)] public void IdentityDeterminantIsOne(int order) { - var I = UserDefinedMatrix.Identity(order); + var I = DenseMatrix.Identity(order); var factorGramSchmidt = I.GramSchmidt(); Assert.AreEqual(1.0, factorGramSchmidt.Determinant); } @@ -115,7 +116,7 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [MultipleAsserts] public void CanFactorizeRandomMatrix(int row, int column) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(row, column); var factorGramSchmidt = matrixA.GramSchmidt(); // Make sure the Q has the right dimensions. @@ -159,11 +160,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [MultipleAsserts] public void CanSolveForRandomVector(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var resultx = factorGramSchmidt.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -196,11 +197,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [MultipleAsserts] public void CanSolveForRandomMatrix(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixX = factorGramSchmidt.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A @@ -239,12 +240,12 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [MultipleAsserts] public void CanSolveForRandomVectorWhenResultVectorGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorb = MatrixLoader.GenerateRandomDenseVector(order); var vectorbCopy = vectorb.Clone(); - var resultx = new UserDefinedVector(order); + var resultx = new DenseVector(order); factorGramSchmidt.Solve(vectorb,resultx); Assert.AreEqual(vectorb.Count, resultx.Count); @@ -283,14 +284,14 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization [MultipleAsserts] public void CanSolveForRandomMatrixWhenResultMatrixGiven(int order) { - var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixA = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixACopy = matrixA.Clone(); var factorGramSchmidt = matrixA.GramSchmidt(); - var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixB = MatrixLoader.GenerateRandomDenseMatrix(order, order); var matrixBCopy = matrixB.Clone(); - var matrixX = new UserDefinedMatrix(order, order); + var matrixX = new DenseMatrix(order, order); factorGramSchmidt.Solve(matrixB,matrixX); // The solution X row dimension is equal to the column dimension of A diff --git a/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserEvdTests.cs index 78936d6b..e37f6bb8 100644 --- a/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserEvdTests.cs +++ b/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserEvdTests.cs @@ -190,10 +190,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); - var resultx = factorSvd.Solve(vectorb); + var resultx = factorEvd.Solve(vectorb); Assert.AreEqual(matrixA.ColumnCount, resultx.Count); @@ -227,10 +227,10 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); - var matrixX = factorSvd.Solve(matrixB); + var matrixX = factorEvd.Solve(matrixB); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); @@ -270,11 +270,11 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); var vectorbCopy = vectorb.Clone(); var resultx = new UserDefinedVector(order); - factorSvd.Solve(vectorb, resultx); + factorEvd.Solve(vectorb, resultx); var bReconstruct = matrixA * resultx; @@ -312,13 +312,13 @@ namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Single.Factorization { var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); var matrixACopy = matrixA.Clone(); - var factorSvd = matrixA.Svd(true); + var factorEvd = matrixA.Evd(); var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); var matrixBCopy = matrixB.Clone(); var matrixX = new UserDefinedMatrix(order, order); - factorSvd.Solve(matrixB, matrixX); + factorEvd.Solve(matrixB, matrixX); // The solution X row dimension is equal to the column dimension of A Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); diff --git a/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserGramSchmidtTests.cs b/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserGramSchmidtTests.cs new file mode 100644 index 00000000..90e3cba0 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserGramSchmidtTests.cs @@ -0,0 +1,331 @@ +// +// 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.Single.Factorization +{ + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Single.Factorization; + + public class UserGramSchmidtTests + { + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserGramSchmidt(null); + } + + [Test] + [ExpectedArgumentException] + public void WideMatrixThrowsInvalidMatrixOperationException() + { + new UserGramSchmidt(new UserDefinedMatrix(3, 4)); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorGramSchmidt = I.GramSchmidt(); + + Assert.AreEqual(I.RowCount, factorGramSchmidt.Q.RowCount); + Assert.AreEqual(I.ColumnCount, factorGramSchmidt.Q.ColumnCount); + + for (var i = 0; i < factorGramSchmidt.R.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.R.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(1.0, factorGramSchmidt.R[i, j]); + } + else + { + Assert.AreEqual(0.0, factorGramSchmidt.R[i, j]); + } + } + } + + for (var i = 0; i < factorGramSchmidt.Q.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.Q.ColumnCount; j++) + { + if (i == j) + { + Assert.AreEqual(1.0, factorGramSchmidt.Q[i, j]); + } + else + { + Assert.AreEqual(0.0, factorGramSchmidt.Q[i, j]); + } + } + } + } + + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void IdentityDeterminantIsOne(int order) + { + var I = UserDefinedMatrix.Identity(order); + var factorGramSchmidt = I.GramSchmidt(); + Assert.AreEqual(1.0, factorGramSchmidt.Determinant); + } + + [Test] + [Row(1,1)] + [Row(2,2)] + [Row(5,5)] + [Row(10,6)] + [Row(50,48)] + [Row(100,98)] + [MultipleAsserts] + public void CanFactorizeRandomMatrix(int row, int column) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(row, column); + var factorGramSchmidt = matrixA.GramSchmidt(); + + // Make sure the Q has the right dimensions. + Assert.AreEqual(row, factorGramSchmidt.Q.RowCount); + Assert.AreEqual(column, factorGramSchmidt.Q.ColumnCount); + + // Make sure the R has the right dimensions. + Assert.AreEqual(column, factorGramSchmidt.R.RowCount); + Assert.AreEqual(column, factorGramSchmidt.R.ColumnCount); + + // Make sure the R factor is upper triangular. + for (var i = 0; i < factorGramSchmidt.R.RowCount; i++) + { + for (var j = 0; j < factorGramSchmidt.R.ColumnCount; j++) + { + if (i > j) + { + Assert.AreEqual(0.0, factorGramSchmidt.R[i, j]); + } + } + } + + // Make sure the Q*R is the original matrix. + var matrixQfromR = factorGramSchmidt.Q * factorGramSchmidt.R; + for (var i = 0; i < matrixQfromR.RowCount; i++) + { + for (var j = 0; j < matrixQfromR.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixA[i, j], matrixQfromR[i, j], 10e-5f); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVector(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorGramSchmidt.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < order; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 10e-5f); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorGramSchmidt.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 10e-5f); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomVectorWhenResultVectorGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorGramSchmidt.Solve(vectorb,resultx); + + Assert.AreEqual(vectorb.Count, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], bReconstruct[i], 10e-5f); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure b didn't change. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreEqual(vectorbCopy[i], vectorb[i]); + } + } + + [Test] + [Row(1)] + [Row(4)] + [Row(8)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void CanSolveForRandomMatrixWhenResultMatrixGiven(int order) + { + var matrixA = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixACopy = matrixA.Clone(); + var factorGramSchmidt = matrixA.GramSchmidt(); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorGramSchmidt.Solve(matrixB,matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], matrixBReconstruct[i, j], 10e-5f); + } + } + + // 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/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 27aee4ab..08ec4cf3 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -110,6 +110,7 @@ + @@ -125,6 +126,8 @@ + + @@ -165,6 +168,8 @@ + + @@ -200,8 +205,10 @@ - + + + @@ -233,6 +240,8 @@ + +