diff --git a/src/Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs new file mode 100644 index 00000000..aa06b67e --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex/Factorization/UserEvd.cs @@ -0,0 +1,959 @@ +// +// 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 UserEvd : 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 UserEvd(Matrix 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.Clone(); + var tau = new Complex[order]; + var d = new double[order]; + var e = new double[order]; + + SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + SymmetricDiagonalize(d, e, order); + SymmetricUntridiagonalize(matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + VectorEv[i] = new Complex(d[i], e[i]); + } + } + else + { + var matrixH = matrix.ToArray(); + NonsymmetricReduceToHessenberg(matrixH, order); + NonsymmetricReduceHessenberToRealSchur(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(Matrix 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++) + { + d[i] = matrixA.Diagonal()[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. + /// + /// 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 void SymmetricDiagonalize(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 = 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); + } + } + + 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 = MatrixEv[j, i].Real; + MatrixEv[j, i] = MatrixEv[j, k]; + MatrixEv[j, k] = p; + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// 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 void SymmetricUntridiagonalize(Matrix 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(); + } + } + + // 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 += MatrixEv[k, j] * matrixA[i, k]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + MatrixEv[k, j] -= s * matrixA[i, k].Conjugate(); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// 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 void NonsymmetricReduceToHessenberg(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++) + { + MatrixEv[i, j] = 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() * MatrixEv[i, j]; + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + MatrixEv[i, j] += 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++) + { + MatrixEv[j, i] *= y; + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// 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 void NonsymmetricReduceHessenberToRealSchur(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; + VectorEv[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; + VectorEv[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 = VectorEv[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 = 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); + } + } + + if (s.Imaginary != 0.0) + { + for (var i = 0; i <= n; i++) + { + matrixH[i, n] *= s; + } + + for (var i = 0; i < order; i++) + { + MatrixEv[i, n] *= 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 = VectorEv[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 - VectorEv[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 += MatrixEv[i, k] * matrixH[k, j]; + } + + MatrixEv[i, j] = 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 += MatrixEv.At(i, j) * 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 += MatrixEv.At(j, i).Conjugate() * 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 += MatrixEv.At(i, j) * 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 += MatrixEv.At(j, i).Conjugate() * 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/Complex32/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs new file mode 100644 index 00000000..57d56040 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Complex32/Factorization/UserEvd.cs @@ -0,0 +1,963 @@ +// +// 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 UserEvd : 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 UserEvd(Matrix 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.Clone(); + var tau = new Complex32[order]; + var d = new float[order]; + var e = new float[order]; + + SymmetricTridiagonalize(matrixCopy, d, e, tau, order); + SymmetricDiagonalize(d, e, order); + SymmetricUntridiagonalize(matrixCopy, tau, order); + + for (var i = 0; i < order; i++) + { + VectorEv[i] = new Complex(d[i], e[i]); + } + } + else + { + var matrixH = matrix.ToArray(); + NonsymmetricReduceToHessenberg(matrixH, order); + NonsymmetricReduceHessenberToRealSchur(matrixH, order); + } + + for (var i = 0; i < VectorEv.Count; i++) + { + MatrixD[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(Matrix 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++) + { + d[i] = matrixA.Diagonal()[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. + /// + /// 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 void SymmetricDiagonalize(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 = 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); + } + } + + 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 = MatrixEv[j, i].Real; + MatrixEv[j, i] = MatrixEv[j, k]; + MatrixEv[j, k] = p; + } + } + } + } + + /// + /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation + /// + /// 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 void SymmetricUntridiagonalize(Matrix 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(); + } + } + + // 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 += MatrixEv[k, j] * matrixA[i, k]; + } + + s = (s / h) / h; + + for (var k = 0; k < i; k++) + { + MatrixEv[k, j] -= s * matrixA[i, k].Conjugate(); + } + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// 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 void NonsymmetricReduceToHessenberg(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++) + { + MatrixEv[i, j] = 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() * MatrixEv[i, j]; + } + + // Double division avoids possible underflow + g /= norm; + for (var i = m; i < order; i++) + { + MatrixEv[i, j] += 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++) + { + MatrixEv[j, i] *= y; + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// 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 void NonsymmetricReduceHessenberToRealSchur(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; + VectorEv[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; + VectorEv[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)VectorEv[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 = 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); + } + } + + if (s.Imaginary != 0.0f) + { + for (var i = 0; i <= n; i++) + { + matrixH[i, n] *= s; + } + + for (var i = 0; i < order; i++) + { + MatrixEv[i, n] *= 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)VectorEv[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)VectorEv[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 += MatrixEv[i, k] * matrixH[k, j]; + } + + MatrixEv[i, j] = 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 += MatrixEv.At(i, j) * 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 += MatrixEv.At(j, i).Conjugate() * 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 += MatrixEv.At(i, j) * 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 += MatrixEv.At(j, i).Conjugate() * 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/Double/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs index cef5a57c..c6acd360 100644 --- a/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs +++ b/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs @@ -436,16 +436,13 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization /// Fortran subroutines in EISPACK. private void NonsymmetricReduceToHessenberg(double[,] matrixH, int order) { - const int Low = 0; - var high = order - 1; - var ort = new double[order]; - for (var m = Low + 1; m <= high - 1; m++) + for (var m = 1; m < order - 1; m++) { // Scale column. var scale = 0.0; - for (var i = m; i <= high; i++) + for (var i = m; i < order; i++) { scale = scale + Math.Abs(matrixH[i, m - 1]); } @@ -454,7 +451,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization { // Compute Householder transformation. var h = 0.0; - for (var i = high; i >= m; i--) + for (var i = order - 1; i >= m; i--) { ort[i] = matrixH[i, m - 1] / scale; h += ort[i] * ort[i]; @@ -474,28 +471,28 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization for (var j = m; j < order; j++) { var f = 0.0; - for (var i = high; i >= m; i--) + for (var i = order - 1; i >= m; i--) { f += ort[i] * matrixH[i, j]; } f = f / h; - for (var i = m; i <= high; i++) + for (var i = m; i < order; i++) { matrixH[i, j] -= f * ort[i]; } } - for (var i = 0; i <= high; i++) + for (var i = 0; i < order; i++) { var f = 0.0; - for (var j = high; j >= m; j--) + for (var j = order - 1; j >= m; j--) { f += ort[j] * matrixH[i, j]; } f = f / h; - for (var j = m; j <= high; j++) + for (var j = m; j < order; j++) { matrixH[i, j] -= f * ort[j]; } @@ -515,26 +512,26 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } } - for (var m = high - 1; m >= Low + 1; m--) + for (var m = order - 2; m >= 1; m--) { if (matrixH[m, m - 1] != 0.0) { - for (var i = m + 1; i <= high; i++) + for (var i = m + 1; i < order; i++) { ort[i] = matrixH[i, m - 1]; } - for (var j = m; j <= high; j++) + for (var j = m; j < order; j++) { var g = 0.0; - for (var i = m; i <= high; i++) + for (var i = m; i < order; i++) { g += ort[i] * MatrixEv[i, j]; } // Double division avoids possible underflow g = (g / ort[m]) / matrixH[m, m - 1]; - for (var i = m; i <= high; i++) + for (var i = m; i < order; i++) { MatrixEv[i, j] += g * ort[i]; } @@ -558,8 +555,6 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization { // Initialize var n = order - 1; - const int Low = 0; - var high = order - 1; var eps = Precision.DoubleMachinePrecision; var exshift = 0.0; double p = 0, q = 0, r = 0, s = 0, z = 0, w, x, y; @@ -568,12 +563,6 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization var norm = 0.0; for (var i = 0; i < order; i++) { - if (i < Low | i > high) - { - d[i] = matrixH[i, i]; - e[i] = 0.0; - } - for (var j = Math.Max(i - 1, 0); j < order; j++) { norm = norm + Math.Abs(matrixH[i, j]); @@ -582,11 +571,11 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization // Outer loop over eigenvalue index var iter = 0; - while (n >= Low) + while (n >= 0) { // Look for single small sub-diagonal element var l = n; - while (l > Low) + while (l > 0) { s = Math.Abs(matrixH[l - 1, l - 1]) + Math.Abs(matrixH[l, l]); @@ -672,7 +661,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } // Accumulate transformations - for (var i = Low; i <= high; i++) + for (var i = 0; i < order; i++) { z = MatrixEv[i, n - 1]; MatrixEv[i, n - 1] = (q * z) + (p * MatrixEv[i, n]); @@ -710,7 +699,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization if (iter == 10) { exshift += x; - for (var i = Low; i <= n; i++) + for (var i = 0; i <= n; i++) { matrixH[i, i] -= x; } @@ -734,7 +723,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } s = x - (w / (((y - x) / 2.0) + s)); - for (var i = Low; i <= n; i++) + for (var i = 0; i <= n; i++) { matrixH[i, i] -= s; } @@ -862,7 +851,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } // Accumulate transformations - for (var i = Low; i <= high; i++) + for (var i = 0; i < order; i++) { p = (x * MatrixEv[i, k]) + (y * MatrixEv[i, k + 1]); @@ -1049,25 +1038,13 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } } - // Vectors of isolated roots - for (var i = 0; i < order; i++) - { - if (i < Low | i > high) - { - for (var j = i; j < order; j++) - { - MatrixEv[i, j] = matrixH[i, j]; - } - } - } - // Back transformation to get eigenvectors of original matrix - for (var j = order - 1; j >= Low; j--) + for (var j = order - 1; j >= 0; j--) { - for (var i = Low; i <= high; i++) + for (var i = 0; i < order; i++) { z = 0.0; - for (var k = Low; k <= Math.Min(j, high); k++) + for (var k = 0; k <= j; k++) { z = z + (MatrixEv[i, k] * matrixH[k, j]); } @@ -1168,7 +1145,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } else { - throw new NotImplementedException(); + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); } } @@ -1238,7 +1215,7 @@ namespace MathNet.Numerics.LinearAlgebra.Double.Factorization } else { - throw new NotImplementedException(); + throw new ArgumentException(Resources.ArgumentMatrixSymmetric); } } diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs index 5ab4176b..f2a26d7a 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs @@ -31,7 +31,6 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization { using System; - using System.Linq; using System.Numerics; using Generic; using Numerics; @@ -103,20 +102,21 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization return new LinearAlgebra.Double.Factorization.UserEvd(matrix as Matrix) as Evd; } - // if (typeof(T) == typeof(float)) - // { - // return new LinearAlgebra.Single.Factorization.UserEvd(matrix as Matrix, computeVectors) as Evd; - // } + if (typeof(T) == typeof(float)) + { + return new LinearAlgebra.Single.Factorization.UserEvd(matrix as Matrix) as Evd; + } - // if (typeof(T) == typeof(Complex)) - // { - // return new LinearAlgebra.Complex.Factorization.UserEvd(matrix as Matrix, computeVectors) as Evd; - // } + if (typeof(T) == typeof(Complex)) + { + return new LinearAlgebra.Complex.Factorization.UserEvd(matrix as Matrix) as Evd; + } + + if (typeof(T) == typeof(Complex32)) + { + return new LinearAlgebra.Complex32.Factorization.UserEvd(matrix as Matrix) as Evd; + } - // if (typeof(T) == typeof(Complex32)) - // { - // return new LinearAlgebra.Complex32.Factorization.UserEvd(matrix as Matrix, computeVectors) as Evd; - // } throw new NotImplementedException(); } @@ -131,9 +131,20 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization for (var i = 0; i < VectorEv.Count; i++) { det *= VectorEv[i]; - if (VectorEv[i].AlmostEqual(Complex.Zero)) + + if (typeof(T) == typeof(float) || typeof(T) == typeof(Complex32)) + { + if (((Complex32)VectorEv[i]).AlmostEqual(Complex32.Zero)) + { + return 0; + } + } + else { - return 0; + if (VectorEv[i].AlmostEqual(Complex.Zero)) + { + return 0; + } } } @@ -149,7 +160,28 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization { get { - return VectorEv.Count(t => !t.AlmostEqual(Complex.Zero)); + var rank = 0; + for (var i = 0; i < VectorEv.Count; i++) + { + if (typeof(T) == typeof(float) || typeof(T) == typeof(Complex32)) + { + if (((Complex32)VectorEv[i]).AlmostEqual(Complex32.Zero)) + { + continue; + } + } + else + { + if (VectorEv[i].AlmostEqual(Complex.Zero)) + { + continue; + } + } + + rank++; + } + + return rank; } } @@ -252,43 +284,7 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization /// Right operand value /// Result of multiplication protected abstract T MultiplyT(T val1, T val2); - - /// - /// Gets value of type T equal to one - /// - /// One value - private static T OneValueT - { - get - { - if (typeof(T) == typeof(Complex)) - { - object one = Complex.One; - return (T)one; - } - - if (typeof(T) == typeof(Complex32)) - { - object one = Complex32.One; - return (T)one; - } - - if (typeof(T) == typeof(double)) - { - object one = 1.0d; - return (T)one; - } - - if (typeof(T) == typeof(float)) - { - object one = 1.0f; - return (T)one; - } - - throw new NotSupportedException(); - } - } - + #endregion } } diff --git a/src/Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs new file mode 100644 index 00000000..23407184 --- /dev/null +++ b/src/Numerics/LinearAlgebra/Single/Factorization/UserEvd.cs @@ -0,0 +1,1234 @@ +// +// 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 UserEvd : 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 UserEvd(Matrix 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(d, e, order); + SymmetricDiagonalize(d, e, order); + } + else + { + var matrixH = matrix.ToArray(); + + NonsymmetricReduceToHessenberg(matrixH, order); + NonsymmetricReduceHessenberToRealSchur(matrixH, d, e, order); + } + + for (var i = 0; i < order; i++) + { + MatrixD[i, i] = d[i]; + + if (e[i] > 0) + { + MatrixD[i, i + 1] = e[i]; + } + else if (e[i] < 0) + { + MatrixD[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. + /// + /// 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 void SymmetricTridiagonalize(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] = MatrixEv[i - 1, j]; + MatrixEv[i, j] = 0.0f; + MatrixEv[j, i] = 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]; + MatrixEv[j, i] = f; + g = e[j] + (MatrixEv[j, j] * f); + + for (var k = j + 1; k <= i - 1; k++) + { + g += MatrixEv[k, j] * d[k]; + e[k] += MatrixEv[k, j] * 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++) + { + MatrixEv[k, j] -= (f * e[k]) + (g * d[k]); + } + + d[j] = MatrixEv[i - 1, j]; + MatrixEv[i, j] = 0.0f; + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + MatrixEv[order - 1, i] = MatrixEv[i, i]; + MatrixEv[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; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0f; + for (var k = 0; k <= i; k++) + { + g += MatrixEv[k, i + 1] * MatrixEv[k, j]; + } + + for (var k = 0; k <= i; k++) + { + MatrixEv[k, j] -= g * d[k]; + } + } + } + + for (var k = 0; k <= i; k++) + { + MatrixEv[k, i + 1] = 0.0f; + } + } + + for (var j = 0; j < order; j++) + { + d[j] = MatrixEv[order - 1, j]; + MatrixEv[order - 1, j] = 0.0f; + } + + MatrixEv[order - 1, order - 1] = 1.0f; + e[0] = 0.0f; + } + + /// + /// Symmetric tridiagonal QL algorithm. + /// + /// 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 void SymmetricDiagonalize(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 = MatrixEv[k, i + 1]; + MatrixEv[k, i + 1] = (s * MatrixEv[k, i]) + (c * h); + MatrixEv[k, i] = (c * MatrixEv[k, i]) - (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 = MatrixEv[j, i]; + MatrixEv[j, i] = MatrixEv[j, k]; + MatrixEv[j, k] = p; + } + } + } + } + + /// + /// Nonsymmetric reduction to Hessenberg form. + /// + /// 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 void NonsymmetricReduceToHessenberg(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++) + { + MatrixEv[i, j] = 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] * MatrixEv[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]; + } + } + } + } + } + + /// + /// Nonsymmetric reduction from Hessenberg to real Schur form. + /// + /// 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 void NonsymmetricReduceHessenberToRealSchur(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 = MatrixEv[i, n - 1]; + MatrixEv[i, n - 1] = (q * z) + (p * MatrixEv[i, n]); + MatrixEv[i, n] = (q * MatrixEv[i, n]) - (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 * MatrixEv[i, k]) + (y * MatrixEv[i, k + 1]); + + if (notlast) + { + p = p + (z * MatrixEv[i, k + 2]); + MatrixEv[i, k + 2] = MatrixEv[i, k + 2] - (p * r); + } + + MatrixEv[i, k] = MatrixEv[i, k] - p; + MatrixEv[i, k + 1] = MatrixEv[i, k + 1] - (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 + (MatrixEv[i, k] * matrixH[k, j]); + } + + MatrixEv[i, j] = 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 += MatrixEv.At(i, j) * 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 += MatrixEv.At(j, i) * 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 += MatrixEv.At(i, j) * 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 += MatrixEv.At(j, i) * 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/Numerics.csproj b/src/Numerics/Numerics.csproj index cb70bbc9..3857f491 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -135,6 +135,7 @@ + @@ -164,6 +165,7 @@ + @@ -198,6 +200,7 @@ + diff --git a/src/Numerics/Precision.cs b/src/Numerics/Precision.cs index f78c7ca9..53042a3d 100644 --- a/src/Numerics/Precision.cs +++ b/src/Numerics/Precision.cs @@ -786,6 +786,17 @@ namespace MathNet.Numerics return AlmostEqualWithError(a.Norm(), b.Norm(), diff, _defaultDoubleRelativeAccuracy); } + /// + /// Checks whether two Compex numbers are almost equal. + /// + /// The first number + /// The second number + /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. + public static bool AlmostEqual(this Complex32 a, Complex32 b) + { + double diff = ((IPrecisionSupport)a).NormOfDifference(b); + return AlmostEqualWithError(((IPrecisionSupport)a).Norm(), ((IPrecisionSupport)b).Norm(), diff, _defaultSingleRelativeAccuracy); + } /// /// Checks whether two structures with precision support are almost equal. /// diff --git a/src/Silverlight/Silverlight.csproj b/src/Silverlight/Silverlight.csproj index e0190c7e..9bce5c4e 100644 --- a/src/Silverlight/Silverlight.csproj +++ b/src/Silverlight/Silverlight.csproj @@ -188,6 +188,9 @@ Distributions\Multivariate\Wishart.cs + + Distributions\Multivariate\Wishart.cs + GlobalizationHelper.cs @@ -299,6 +302,9 @@ LinearAlgebra\Complex32\Factorization\UserCholesky.cs + + LinearAlgebra\Complex32\Factorization\UserEvd.cs + LinearAlgebra\Complex32\Factorization\UserLU.cs @@ -386,6 +392,9 @@ LinearAlgebra\Complex\Factorization\UserCholesky.cs + + LinearAlgebra\Complex\Factorization\UserEvd.cs + LinearAlgebra\Complex\Factorization\UserLU.cs @@ -590,6 +599,9 @@ LinearAlgebra\Single\Factorization\UserCholesky.cs + + LinearAlgebra\Single\Factorization\UserEvd.cs + LinearAlgebra\Single\Factorization\UserLU.cs diff --git a/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserEvdTests.cs new file mode 100644 index 00000000..b7012729 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex/Factorization/UserEvdTests.cs @@ -0,0 +1,363 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.UnitTests.LinearAlgebraTests.Complex.Factorization +{ + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex.Factorization; + + public class UserEvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserEvd(null); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.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.GenerateRandomUserDefinedMatrix(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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(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.GenerateRandomUserDefinedMatrix(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 UserDefinedMatrix(order, order); + matrixA[0, 0] = 1; + matrixA[order - 1, order - 1] = 1; + for (var i = 1; i < order - 1; i++) + { + matrixA[i, i - 1] = 1; + matrixA[i, i + 1] = 1; + matrixA[i - 1, i] = 1; + matrixA[i + 1, i] = 1; + } + var 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 = UserDefinedMatrix.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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorSvd.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorSvd.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j].Real, matrixBReconstruct[i, j].Real, 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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorSvd.Solve(vectorb, resultx); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorSvd.Solve(matrixB, matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j].Real, matrixBReconstruct[i, j].Real, 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/Complex32/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserEvdTests.cs new file mode 100644 index 00000000..6a6f45a9 --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Complex32/Factorization/UserEvdTests.cs @@ -0,0 +1,363 @@ +// +// 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.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Complex.Factorization; + + public class UserEvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserEvd(null); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.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.GenerateRandomUserDefinedMatrix(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] + public void CanFactorizeRandomSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(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.GenerateRandomUserDefinedMatrix(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 UserDefinedMatrix(order, order); + matrixA[0, 0] = 1; + matrixA[order - 1, order - 1] = 1; + for (var i = 1; i < order - 1; i++) + { + matrixA[i, i - 1] = 1; + matrixA[i, i + 1] = 1; + matrixA[i - 1, i] = 1; + matrixA[i + 1, i] = 1; + } + var 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 = UserDefinedMatrix.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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorSvd.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorSvd.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j].Real, matrixBReconstruct[i, j].Real, 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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorSvd.Solve(vectorb, resultx); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i].Real, bReconstruct[i].Real, 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.GenerateRandomPositiveDefiniteHermitianUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorSvd.Solve(matrixB, matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j].Real, matrixBReconstruct[i, j].Real, 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/Single/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserEvdTests.cs new file mode 100644 index 00000000..507178de --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Single/Factorization/UserEvdTests.cs @@ -0,0 +1,357 @@ +// +// 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 MbUnit.Framework; + using LinearAlgebra.Single.Factorization; + + public class UserEvdTests + { + + [Test] + [ExpectedArgumentNullException] + public void ConstructorNull() + { + new UserEvd(null); + } + + [Test] + [Row(1)] + [Row(10)] + [Row(100)] + public void CanFactorizeIdentity(int order) + { + var I = UserDefinedMatrix.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.GenerateRandomUserDefinedMatrix(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)] + [MultipleAsserts] + public void CanFactorizeRandomSymmetricMatrix(int order) + { + var matrixA = MatrixLoader.GenerateRandomPositiveDefiniteUserDefinedMatrix(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.GenerateRandomUserDefinedMatrix(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 UserDefinedMatrix(order, order); + matrixA[0, 0] = 1; + matrixA[order - 1, order - 1] = 1; + for (var i = 1; i < order - 1; i++) + { + matrixA[i, i - 1] = 1; + matrixA[i, i + 1] = 1; + matrixA[i - 1, i] = 1; + matrixA[i + 1, i] = 1; + } + var 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 = UserDefinedMatrix.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.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var resultx = factorSvd.Solve(vectorb); + + Assert.AreEqual(matrixA.ColumnCount, resultx.Count); + + var bReconstruct = matrixA * resultx; + + // Check the reconstruction. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreApproximatelyEqual(vectorb[i], 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.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixX = factorSvd.Solve(matrixB); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], 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.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + var vectorb = MatrixLoader.GenerateRandomUserDefinedVector(order); + var vectorbCopy = vectorb.Clone(); + var resultx = new UserDefinedVector(order); + factorSvd.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.GenerateRandomPositiveDefiniteUserDefinedMatrix(order); + var matrixACopy = matrixA.Clone(); + var factorSvd = matrixA.Svd(true); + + var matrixB = MatrixLoader.GenerateRandomUserDefinedMatrix(order, order); + var matrixBCopy = matrixB.Clone(); + + var matrixX = new UserDefinedMatrix(order, order); + factorSvd.Solve(matrixB, matrixX); + + // The solution X row dimension is equal to the column dimension of A + Assert.AreEqual(matrixA.ColumnCount, matrixX.RowCount); + // The solution X has the same number of columns as B + Assert.AreEqual(matrixB.ColumnCount, matrixX.ColumnCount); + + var matrixBReconstruct = matrixA * matrixX; + + // Check the reconstruction. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreApproximatelyEqual(matrixB[i, j], 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/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 265a0fe7..97b072ec 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -129,6 +129,7 @@ + @@ -168,6 +169,7 @@ + @@ -235,6 +237,7 @@ +