diff --git a/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs b/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs new file mode 100644 index 00000000..cef5a57c --- /dev/null +++ b/src/Numerics/LinearAlgebra/Double/Factorization/UserEvd.cs @@ -0,0 +1,1256 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +namespace MathNet.Numerics.LinearAlgebra.Double.Factorization +{ + using System; + using System.Numerics; + using Generic; + using Generic.Factorization; + using Properties; + + /// + /// Eigenvalues and eigenvectors of a real matrix. + /// + /// + /// If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is + /// diagonal and the eigenvector matrix V is orthogonal. + /// I.e. A = V*D*V' and V*VT=I. + /// If A is not symmetric, then the eigenvalue matrix D is block diagonal + /// with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues, + /// lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda]. The + /// columns of V represent the eigenvectors in the sense that A*V = V*D, + /// i.e. A.Multiply(V) equals V.Multiply(D). The matrix V may be badly + /// conditioned, or even singular, so the validity of the equation + /// A = V*D*Inverse(V) depends upon V.cond(). + /// + public class 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 double[order]; + var e = new double[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(double[] d, double[] e, int order) + { + // Householder reduction to tridiagonal form. + for (var i = order - 1; i > 0; i--) + { + // Scale to avoid under/overflow. + var scale = 0.0; + var h = 0.0; + + for (var k = 0; k < i; k++) + { + scale = scale + Math.Abs(d[k]); + } + + if (scale == 0.0) + { + e[i] = d[i - 1]; + for (var j = 0; j < i; j++) + { + d[j] = MatrixEv[i - 1, j]; + MatrixEv[i, j] = 0.0; + MatrixEv[j, i] = 0.0; + } + } + else + { + // Generate Householder vector. + for (var k = 0; k < i; k++) + { + d[k] /= scale; + h += d[k] * d[k]; + } + + var f = d[i - 1]; + var g = Math.Sqrt(h); + if (f > 0) + { + g = -g; + } + + e[i] = scale * g; + h = h - (f * g); + d[i - 1] = f - g; + + for (var j = 0; j < i; j++) + { + e[j] = 0.0; + } + + // Apply similarity transformation to remaining columns. + for (var j = 0; j < i; j++) + { + f = d[j]; + 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.0; + + for (var j = 0; j < i; j++) + { + e[j] /= h; + f += e[j] * d[j]; + } + + var hh = f / (h + h); + + for (var j = 0; j < i; j++) + { + e[j] -= hh * d[j]; + } + + for (var j = 0; j < i; j++) + { + f = d[j]; + g = e[j]; + + for (var k = j; k <= i - 1; k++) + { + MatrixEv[k, j] -= (f * e[k]) + (g * d[k]); + } + + d[j] = MatrixEv[i - 1, j]; + MatrixEv[i, j] = 0.0; + } + } + + d[i] = h; + } + + // Accumulate transformations. + for (var i = 0; i < order - 1; i++) + { + MatrixEv[order - 1, i] = MatrixEv[i, i]; + MatrixEv[i, i] = 1.0; + var h = d[i + 1]; + if (h != 0.0) + { + for (var k = 0; k <= i; k++) + { + d[k] = MatrixEv[k, i + 1] / h; + } + + for (var j = 0; j <= i; j++) + { + var g = 0.0; + 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.0; + } + } + + for (var j = 0; j < order; j++) + { + d[j] = MatrixEv[order - 1, j]; + MatrixEv[order - 1, j] = 0.0; + } + + MatrixEv[order - 1, order - 1] = 1.0; + 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]; + 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.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]; + 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(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++) + { + // Scale column. + var scale = 0.0; + for (var i = m; i <= high; i++) + { + scale = scale + Math.Abs(matrixH[i, m - 1]); + } + + if (scale != 0.0) + { + // Compute Householder transformation. + var h = 0.0; + for (var i = high; i >= m; i--) + { + ort[i] = matrixH[i, m - 1] / scale; + h += ort[i] * ort[i]; + } + + var g = Math.Sqrt(h); + if (ort[m] > 0) + { + g = -g; + } + + h = h - (ort[m] * g); + ort[m] = ort[m] - g; + + // Apply Householder similarity transformation + // H = (I-u*u'/h)*H*(I-u*u')/h) + for (var j = m; j < order; j++) + { + var f = 0.0; + for (var i = high; i >= m; i--) + { + f += ort[i] * matrixH[i, j]; + } + + f = f / h; + for (var i = m; i <= high; i++) + { + matrixH[i, j] -= f * ort[i]; + } + } + + for (var i = 0; i <= high; i++) + { + var f = 0.0; + for (var j = high; j >= m; j--) + { + f += ort[j] * matrixH[i, j]; + } + + f = f / h; + for (var j = m; j <= high; 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.0 : 0.0; + } + } + + for (var m = high - 1; m >= Low + 1; m--) + { + if (matrixH[m, m - 1] != 0.0) + { + for (var i = m + 1; i <= high; i++) + { + ort[i] = matrixH[i, m - 1]; + } + + for (var j = m; j <= high; j++) + { + var g = 0.0; + for (var i = m; i <= high; 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++) + { + 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(double[,] matrixH, double[] d, double[] e, int order) + { + // 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; + + // Store roots isolated by balanc and compute matrix norm + 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]); + } + } + + // Outer loop over eigenvalue index + var iter = 0; + while (n >= Low) + { + // Look for single small sub-diagonal element + var l = n; + while (l > Low) + { + s = Math.Abs(matrixH[l - 1, l - 1]) + Math.Abs(matrixH[l, l]); + + if (s == 0.0) + { + s = norm; + } + + if (Math.Abs(matrixH[l, l - 1]) < eps * s) + { + break; + } + + l--; + } + + // Check for convergence + // One root found + if (l == n) + { + matrixH[n, n] = matrixH[n, n] + exshift; + d[n] = matrixH[n, n]; + e[n] = 0.0; + n--; + iter = 0; + + // Two roots found + } + else if (l == n - 1) + { + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + p = (matrixH[n - 1, n - 1] - matrixH[n, n]) / 2.0; + q = (p * p) + w; + z = Math.Sqrt(Math.Abs(q)); + matrixH[n, n] = matrixH[n, n] + exshift; + matrixH[n - 1, n - 1] = matrixH[n - 1, n - 1] + exshift; + x = matrixH[n, n]; + + // Real pair + if (q >= 0) + { + if (p >= 0) + { + z = p + z; + } + else + { + z = p - z; + } + + d[n - 1] = x + z; + + d[n] = d[n - 1]; + if (z != 0.0) + { + d[n] = x - (w / z); + } + + e[n - 1] = 0.0; + e[n] = 0.0; + x = matrixH[n, n - 1]; + s = Math.Abs(x) + Math.Abs(z); + p = x / s; + q = z / s; + r = Math.Sqrt((p * p) + (q * q)); + p = p / r; + q = q / r; + + // Row modification + for (var j = n - 1; j < order; j++) + { + z = matrixH[n - 1, j]; + matrixH[n - 1, j] = (q * z) + (p * matrixH[n, j]); + matrixH[n, j] = (q * matrixH[n, j]) - (p * z); + } + + // Column modification + for (var i = 0; i <= n; i++) + { + z = matrixH[i, n - 1]; + matrixH[i, n - 1] = (q * z) + (p * matrixH[i, n]); + matrixH[i, n] = (q * matrixH[i, n]) - (p * z); + } + + // Accumulate transformations + for (var i = Low; i <= high; 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.0; + w = 0.0; + if (l < n) + { + y = matrixH[n - 1, n - 1]; + w = matrixH[n, n - 1] * matrixH[n - 1, n]; + } + + // Wilkinson's original ad hoc shift + if (iter == 10) + { + exshift += x; + for (var i = Low; i <= n; i++) + { + matrixH[i, i] -= x; + } + + s = Math.Abs(matrixH[n, n - 1]) + Math.Abs(matrixH[n - 1, n - 2]); + x = y = 0.75 * s; + w = (-0.4375) * s * s; + } + + // MATLAB's new ad hoc shift + if (iter == 30) + { + s = (y - x) / 2.0; + s = (s * s) + w; + if (s > 0) + { + s = Math.Sqrt(s); + if (y < x) + { + s = -s; + } + + s = x - (w / (((y - x) / 2.0) + s)); + for (var i = Low; i <= n; i++) + { + matrixH[i, i] -= s; + } + + exshift += s; + x = y = w = 0.964; + } + } + + iter = iter + 1; // (Could check iteration count here.) + + // Look for two consecutive small sub-diagonal elements + var m = n - 2; + while (m >= l) + { + z = matrixH[m, m]; + r = x - z; + s = y - z; + p = (((r * s) - w) / matrixH[m + 1, m]) + matrixH[m, m + 1]; + q = matrixH[m + 1, m + 1] - z - r - s; + r = matrixH[m + 2, m + 1]; + s = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + p = p / s; + q = q / s; + r = r / s; + + if (m == l) + { + break; + } + + if (Math.Abs(matrixH[m, m - 1]) * (Math.Abs(q) + Math.Abs(r)) < eps * (Math.Abs(p) * (Math.Abs(matrixH[m - 1, m - 1]) + Math.Abs(z) + Math.Abs(matrixH[m + 1, m + 1])))) + { + break; + } + + m--; + } + + for (var i = m + 2; i <= n; i++) + { + matrixH[i, i - 2] = 0.0; + if (i > m + 2) + { + matrixH[i, i - 3] = 0.0; + } + } + + // Double QR step involving rows l:n and columns m:n + for (var k = m; k <= n - 1; k++) + { + bool notlast = k != n - 1; + + if (k != m) + { + p = matrixH[k, k - 1]; + q = matrixH[k + 1, k - 1]; + r = notlast ? matrixH[k + 2, k - 1] : 0.0; + x = Math.Abs(p) + Math.Abs(q) + Math.Abs(r); + if (x != 0.0) + { + p = p / x; + q = q / x; + r = r / x; + } + } + + if (x == 0.0) + { + break; + } + + s = Math.Sqrt((p * p) + (q * q) + (r * r)); + if (p < 0) + { + s = -s; + } + + if (s != 0.0) + { + if (k != m) + { + matrixH[k, k - 1] = (-s) * x; + } + else if (l != m) + { + matrixH[k, k - 1] = -matrixH[k, k - 1]; + } + + p = p + s; + x = p / s; + y = q / s; + z = r / s; + q = q / p; + r = r / p; + + // Row modification + for (var j = k; j < order; j++) + { + p = matrixH[k, j] + (q * matrixH[k + 1, j]); + + if (notlast) + { + p = p + (r * matrixH[k + 2, j]); + matrixH[k + 2, j] = matrixH[k + 2, j] - (p * z); + } + + matrixH[k, j] = matrixH[k, j] - (p * x); + matrixH[k + 1, j] = matrixH[k + 1, j] - (p * y); + } + + // Column modification + for (var i = 0; i <= Math.Min(n, k + 3); i++) + { + p = (x * matrixH[i, k]) + (y * matrixH[i, k + 1]); + + if (notlast) + { + p = p + (z * matrixH[i, k + 2]); + matrixH[i, k + 2] = matrixH[i, k + 2] - (p * r); + } + + matrixH[i, k] = matrixH[i, k] - p; + matrixH[i, k + 1] = matrixH[i, k + 1] - (p * q); + } + + // Accumulate transformations + for (var i = Low; i <= high; 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.0) + { + return; + } + + for (n = order - 1; n >= 0; n--) + { + double t; + + p = d[n]; + q = e[n]; + + // Real vector + if (q == 0.0) + { + var l = n; + matrixH[n, n] = 1.0; + for (var i = n - 1; i >= 0; i--) + { + w = matrixH[i, i] - p; + r = 0.0; + for (var j = l; j <= n; j++) + { + r = r + (matrixH[i, j] * matrixH[j, n]); + } + + if (e[i] < 0.0) + { + z = w; + s = r; + } + else + { + l = i; + if (e[i] == 0.0) + { + if (w != 0.0) + { + matrixH[i, n] = (-r) / w; + } + else + { + matrixH[i, n] = (-r) / (eps * norm); + } + + // Solve real equations + } + else + { + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + q = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]); + t = ((x * s) - (z * r)) / q; + matrixH[i, n] = t; + if (Math.Abs(x) > Math.Abs(z)) + { + matrixH[i + 1, n] = (-r - (w * t)) / x; + } + else + { + matrixH[i + 1, n] = (-s - (y * t)) / z; + } + } + + // Overflow control + t = Math.Abs(matrixH[i, n]); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + + // Complex vector + } + else if (q < 0) + { + var l = n - 1; + + // Last vector component imaginary so matrix is triangular + if (Math.Abs(matrixH[n, n - 1]) > Math.Abs(matrixH[n - 1, n])) + { + matrixH[n - 1, n - 1] = q / matrixH[n, n - 1]; + matrixH[n - 1, n] = (-(matrixH[n, n] - p)) / matrixH[n, n - 1]; + } + else + { + var res = Cdiv(0.0, -matrixH[n - 1, n], matrixH[n - 1, n - 1] - p, q); + matrixH[n - 1, n - 1] = res.Real; + matrixH[n - 1, n] = res.Imaginary; + } + + matrixH[n, n - 1] = 0.0; + matrixH[n, n] = 1.0; + for (var i = n - 2; i >= 0; i--) + { + double ra = 0.0; + double sa = 0.0; + for (var j = l; j <= n; j++) + { + ra = ra + (matrixH[i, j] * matrixH[j, n - 1]); + sa = sa + (matrixH[i, j] * matrixH[j, n]); + } + + w = matrixH[i, i] - p; + + if (e[i] < 0.0) + { + z = w; + r = ra; + s = sa; + } + else + { + l = i; + if (e[i] == 0.0) + { + var res = Cdiv(-ra, -sa, w, q); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + } + else + { + // Solve complex equations + x = matrixH[i, i + 1]; + y = matrixH[i + 1, i]; + + double vr = ((d[i] - p) * (d[i] - p)) + (e[i] * e[i]) - (q * q); + double vi = (d[i] - p) * 2.0 * q; + if ((vr == 0.0) && (vi == 0.0)) + { + vr = eps * norm * (Math.Abs(w) + Math.Abs(q) + Math.Abs(x) + Math.Abs(y) + Math.Abs(z)); + } + + var res = Cdiv((x * r) - (z * ra) + (q * sa), (x * s) - (z * sa) - (q * ra), vr, vi); + matrixH[i, n - 1] = res.Real; + matrixH[i, n] = res.Imaginary; + if (Math.Abs(x) > (Math.Abs(z) + Math.Abs(q))) + { + matrixH[i + 1, n - 1] = (-ra - (w * matrixH[i, n - 1]) + (q * matrixH[i, n])) / x; + matrixH[i + 1, n] = (-sa - (w * matrixH[i, n]) - (q * matrixH[i, n - 1])) / x; + } + else + { + res = Cdiv(-r - (y * matrixH[i, n - 1]), -s - (y * matrixH[i, n]), z, q); + matrixH[i + 1, n - 1] = res.Real; + matrixH[i + 1, n] = res.Imaginary; + } + } + + // Overflow control + t = Math.Max(Math.Abs(matrixH[i, n - 1]), Math.Abs(matrixH[i, n])); + if ((eps * t) * t > 1) + { + for (var j = i; j <= n; j++) + { + matrixH[j, n - 1] = matrixH[j, n - 1] / t; + matrixH[j, n] = matrixH[j, n] / t; + } + } + } + } + } + } + + // 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 i = Low; i <= high; i++) + { + z = 0.0; + for (var k = Low; k <= Math.Min(j, high); 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 Complex Cdiv(double xreal, double ximag, double yreal, double yimag) + { + if (Math.Abs(yimag) < Math.Abs(yreal)) + { + return new Complex((xreal + (ximag * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal))), (ximag - (xreal * (yimag / yreal))) / (yreal + (yimag * (yimag / yreal)))); + } + + return new Complex((ximag + (xreal * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag))), (-xreal + (ximag * (yreal / yimag))) / (yimag + (yreal * (yreal / yimag)))); + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public override void Solve(Matrix input, Matrix result) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // The solution X should have the same number of columns as B + if (input.ColumnCount != result.ColumnCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + // The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows + if (VectorEv.Count != input.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameRowDimension); + } + + // The solution X row dimension is equal to the column dimension of A + if (VectorEv.Count != result.RowCount) + { + throw new ArgumentException(Resources.ArgumentMatrixSameColumnDimension); + } + + if (IsSymmetric) + { + var order = VectorEv.Count; + var tmp = new double[order]; + + for (var k = 0; k < order; k++) + { + for (var j = 0; j < order; j++) + { + double value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += MatrixEv.At(i, j) * input.At(i, k); + } + + value /= VectorEv[j].Real; + } + + tmp[j] = value; + } + + for (var j = 0; j < order; j++) + { + double value = 0; + for (var i = 0; i < order; i++) + { + value += MatrixEv.At(j, i) * tmp[i]; + } + + result[j, k] = value; + } + } + } + else + { + throw new NotImplementedException(); + } + } + + /// + /// Solves a system of linear equations, Ax = b, with A EVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public override void Solve(Vector input, Vector result) + { + if (input == null) + { + throw new ArgumentNullException("input"); + } + + if (result == null) + { + throw new ArgumentNullException("result"); + } + + // Ax=b where A is an m x m matrix + // Check that b is a column vector with m entries + if (VectorEv.Count != input.Count) + { + throw new ArgumentException(Resources.ArgumentVectorsSameLength); + } + + // Check that x is a column vector with n entries + if (VectorEv.Count != result.Count) + { + throw new ArgumentException(Resources.ArgumentMatrixDimensions); + } + + if (IsSymmetric) + { + // Symmetric case -> x = V * inv(λ) * VT * b; + var order = VectorEv.Count; + var tmp = new double[order]; + double value; + + for (var j = 0; j < order; j++) + { + value = 0; + if (j < order) + { + for (var i = 0; i < order; i++) + { + value += 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) * tmp[i]; + } + + result[j] = value; + } + } + else + { + throw new NotImplementedException(); + } + } + + /// + /// Multiply two values T*T + /// + /// Left operand value + /// Right operand value + /// Result of multiplication + protected sealed override double MultiplyT(double val1, double val2) + { + return val1 * val2; + } + } +} \ No newline at end of file diff --git a/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs new file mode 100644 index 00000000..5ab4176b --- /dev/null +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/Evd.cs @@ -0,0 +1,294 @@ +// +// Math.NET Numerics, part of the Math.NET Project +// http://numerics.mathdotnet.com +// http://github.com/mathnet/mathnet-numerics +// http://mathnetnumerics.codeplex.com +// +// Copyright (c) 2009-2010 Math.NET +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// + +namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization +{ + using System; + using System.Linq; + using System.Numerics; + using Generic; + using Numerics; + + /// + /// 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(). + /// + /// Supported data types are double, single, , and . + public abstract class Evd : ISolver + where T : struct, IEquatable, IFormattable + { + /// + /// Gets or sets a value indicating whether matrix is symmetric or not + /// + public bool IsSymmetric + { + get; + protected set; + } + + /// + /// Gets or sets the eigen values (λ) of matrix in ascending value. + /// + protected Vector VectorEv + { + get; + set; + } + + /// + /// Gets or sets eigenvectors. + /// + protected Matrix MatrixEv + { + get; + set; + } + + /// + /// Gets or sets the block diagonal eigenvalue matrix. + /// + protected Matrix MatrixD + { + get; + set; + } + + /// + /// Internal method which routes the call to perform the singular value decomposition to the appropriate class. + /// + /// The matrix to factor. + /// An EVD object. + internal static Evd Create(Matrix matrix) + { + if (typeof(T) == typeof(double)) + { + 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(Complex)) + // { + // return new LinearAlgebra.Complex.Factorization.UserEvd(matrix as Matrix, computeVectors) as Evd; + // } + + // if (typeof(T) == typeof(Complex32)) + // { + // return new LinearAlgebra.Complex32.Factorization.UserEvd(matrix as Matrix, computeVectors) as Evd; + // } + throw new NotImplementedException(); + } + + /// + /// Gets the absolute value of determinant of the square matrix for which the EVD was computed. + /// + public virtual double Determinant + { + get + { + var det = Complex.One; + for (var i = 0; i < VectorEv.Count; i++) + { + det *= VectorEv[i]; + if (VectorEv[i].AlmostEqual(Complex.Zero)) + { + return 0; + } + } + + return det.Magnitude; + } + } + + /// + /// Gets the effective numerical matrix rank. + /// + /// The number of non-negligible singular values. + public virtual int Rank + { + get + { + return VectorEv.Count(t => !t.AlmostEqual(Complex.Zero)); + } + } + + /// + /// Gets a value indicating whether the matrix is full rank or not. + /// + /// true if the matrix is full rank; otherwise false. + public virtual bool IsFullRank + { + get + { + for (var i = 0; i < VectorEv.Count; i++) + { + if (VectorEv[i].AlmostEqual(Complex.Zero)) + { + return false; + } + } + + return true; + } + } + + /// Returns the eigen values as a . + /// The eigen values. + public Vector EValues() + { + return VectorEv.Clone(); + } + + /// Returns the right eigen vectors as a . + /// The eigen vectors. + public Matrix EVectors() + { + return MatrixEv.Clone(); + } + + /// Returns the block diagonal eigenvalue matrix . + /// The block diagonal eigenvalue matrix . + public Matrix D() + { + return MatrixD.Clone(); + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public virtual Matrix Solve(Matrix input) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var result = MatrixEv.CreateMatrix(MatrixEv.ColumnCount, input.ColumnCount); + Solve(input, result); + return result; + } + + /// + /// Solves a system of linear equations, AX = B, with A SVD factorized. + /// + /// The right hand side , B. + /// The left hand side , X. + public abstract void Solve(Matrix input, Matrix result); + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public virtual Vector Solve(Vector input) + { + // Check for proper arguments. + if (input == null) + { + throw new ArgumentNullException("input"); + } + + var x = MatrixEv.CreateVector(MatrixEv.ColumnCount); + Solve(input, x); + return x; + } + + /// + /// Solves a system of linear equations, Ax = b, with A SVD factorized. + /// + /// The right hand side vector, b. + /// The left hand side , x. + public abstract void Solve(Vector input, Vector result); + + #region Simple arithmetic of type T + /// + /// Multiply two values T*T + /// + /// Left operand value + /// 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/Generic/Factorization/ExtensionMethods.cs b/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs index 9b908390..0f89e881 100644 --- a/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs +++ b/src/Numerics/LinearAlgebra/Generic/Factorization/ExtensionMethods.cs @@ -109,11 +109,22 @@ namespace MathNet.Numerics.LinearAlgebra.Generic.Factorization /// /// The matrix to factor. /// Compute the singular U and VT vectors or not. - /// The QR decomposition object. + /// The SVD decomposition object. /// Supported data types are double, single, , and . public static Svd Svd(this Matrix matrix, bool computeVectors) where T : struct, IEquatable, IFormattable { return Factorization.Svd.Create(matrix, computeVectors); } + + /// + /// Computes the EVD decomposition for a matrix. + /// + /// The matrix to factor. + /// The EVD decomposition object. + /// Supported data types are double, single, , and . + public static Evd Evd(this Matrix matrix) where T : struct, IEquatable, IFormattable + { + return Factorization.Evd.Create(matrix); + } } } diff --git a/src/Numerics/Numerics.csproj b/src/Numerics/Numerics.csproj index 12487fa1..5cba634b 100644 --- a/src/Numerics/Numerics.csproj +++ b/src/Numerics/Numerics.csproj @@ -168,6 +168,8 @@ + + diff --git a/src/Silverlight/Silverlight.csproj b/src/Silverlight/Silverlight.csproj index d79f43aa..cb19f88e 100644 --- a/src/Silverlight/Silverlight.csproj +++ b/src/Silverlight/Silverlight.csproj @@ -428,6 +428,9 @@ LinearAlgebra\Double\Factorization\UserCholesky.cs + + LinearAlgebra\Double\Factorization\UserEvd.cs + LinearAlgebra\Double\Factorization\UserLU.cs @@ -500,6 +503,9 @@ LinearAlgebra\Double\SparseVector.cs + + LinearAlgebra\Generic\Factorization\Evd.cs + LinearAlgebra\Single\DenseMatrix.cs diff --git a/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserEvdTests.cs b/src/UnitTests/LinearAlgebraTests/Double/Factorization/UserEvdTests.cs new file mode 100644 index 00000000..43f13acf --- /dev/null +++ b/src/UnitTests/LinearAlgebraTests/Double/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.Double.Factorization +{ + using System.Numerics; + using LinearAlgebra.Generic.Factorization; + using MbUnit.Framework; + using LinearAlgebra.Double.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], 1.0e-11); + } + } + } + + [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], 1.0e-11); + } + } + } + + [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], 1.0e-11); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void 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], 1.0e-11); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + } + + [Test] + [Row(1)] + [Row(2)] + [Row(5)] + [Row(10)] + [Row(50)] + [Row(100)] + [MultipleAsserts] + public void 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], 1.0e-11); + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure b didn't change. + for (var i = 0; i < vectorb.Count; i++) + { + Assert.AreEqual(vectorbCopy[i], vectorb[i]); + } + } + + [Test] + [Row(1)] + [Row(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], 1.0e-11); + } + } + + // Make sure A didn't change. + for (var i = 0; i < matrixA.RowCount; i++) + { + for (var j = 0; j < matrixA.ColumnCount; j++) + { + Assert.AreEqual(matrixACopy[i, j], matrixA[i, j]); + } + } + + // Make sure B didn't change. + for (var i = 0; i < matrixB.RowCount; i++) + { + for (var j = 0; j < matrixB.ColumnCount; j++) + { + Assert.AreEqual(matrixBCopy[i, j], matrixB[i, j]); + } + } + } + } +} diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index e33c0a39..1136ba89 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -169,6 +169,7 @@ +