// // Math.NET Numerics, part of the Math.NET Project // http://numerics.mathdotnet.com // http://github.com/mathnet/mathnet-numerics // // Copyright (c) 2009-2020 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. // using System; using IStation.Numerics.Threading; using Complex = System.Numerics.Complex; using QRMethod = IStation.Numerics.LinearAlgebra.Factorization.QRMethod; namespace IStation.Numerics.Providers.LinearAlgebra.ManagedReference { /// /// The managed linear algebra provider. /// internal partial class ManagedReferenceLinearAlgebraProvider { /// /// Adds a scaled vector to another: result = y + alpha*x. /// /// The vector to update. /// The value to scale by. /// The vector to add to . /// The result of the addition. /// This is similar to the AXPY BLAS routine. public virtual void AddVectorToScaledVector(double[] y, double alpha, double[] x, double[] result) { if (y == null) { throw new ArgumentNullException(nameof(y)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (y.Length != x.Length) { throw new ArgumentException("All vectors must have the same dimensionality."); } if (alpha == 0.0) { y.Copy(result); } else if (alpha == 1.0) { CommonParallel.For(0, y.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = y[i] + x[i]; } }); } else { CommonParallel.For(0, y.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = y[i] + (alpha * x[i]); } }); } } /// /// Scales an array. Can be used to scale a vector and a matrix. /// /// The scalar. /// The values to scale. /// This result of the scaling. /// This is similar to the SCAL BLAS routine. public virtual void ScaleArray(double alpha, double[] x, double[] result) { if (x == null) { throw new ArgumentNullException(nameof(x)); } if (alpha == 0.0) { Array.Clear(result, 0, result.Length); } else if (alpha == 1.0) { x.Copy(result); } else { CommonParallel.For(0, x.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = alpha * x[i]; } }); } } /// /// Conjugates an array. Can be used to conjugate a vector and a matrix. /// /// The values to conjugate. /// This result of the conjugation. public virtual void ConjugateArray(double[] x, double[] result) { if (x == null) { throw new ArgumentNullException(nameof(x)); } if (!ReferenceEquals(x, result)) { x.CopyTo(result, 0); } } /// /// Computes the dot product of x and y. /// /// The vector x. /// The vector y. /// The dot product of x and y. /// This is equivalent to the DOT BLAS routine. public virtual double DotProduct(double[] x, double[] y) { if (y == null) { throw new ArgumentNullException(nameof(y)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (y.Length != x.Length) { throw new ArgumentException("All vectors must have the same dimensionality."); } var sum = 0.0; for (var index = 0; index < y.Length; index++) { sum += y[index] * x[index]; } return sum; } /// /// Does a point wise add of two arrays z = x + y. This can be used /// to add vectors or matrices. /// /// The array x. /// The array y. /// The result of the addition. /// There is no equivalent BLAS routine, but many libraries /// provide optimized (parallel and/or vectorized) versions of this /// routine. public virtual void AddArrays(double[] x, double[] y, double[] result) { if (y == null) { throw new ArgumentNullException(nameof(y)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (result == null) { throw new ArgumentNullException(nameof(result)); } if (y.Length != x.Length || y.Length != result.Length) { throw new ArgumentException("All vectors must have the same dimensionality."); } CommonParallel.For(0, y.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = x[i] + y[i]; } }); } /// /// Does a point wise subtraction of two arrays z = x - y. This can be used /// to subtract vectors or matrices. /// /// The array x. /// The array y. /// The result of the subtraction. /// There is no equivalent BLAS routine, but many libraries /// provide optimized (parallel and/or vectorized) versions of this /// routine. public virtual void SubtractArrays(double[] x, double[] y, double[] result) { if (y == null) { throw new ArgumentNullException(nameof(y)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (result == null) { throw new ArgumentNullException(nameof(result)); } if (y.Length != x.Length || y.Length != result.Length) { throw new ArgumentException("All vectors must have the same dimensionality."); } CommonParallel.For(0, y.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = x[i] - y[i]; } }); } /// /// Does a point wise multiplication of two arrays z = x * y. This can be used /// to multiple elements of vectors or matrices. /// /// The array x. /// The array y. /// The result of the point wise multiplication. /// There is no equivalent BLAS routine, but many libraries /// provide optimized (parallel and/or vectorized) versions of this /// routine. public virtual void PointWiseMultiplyArrays(double[] x, double[] y, double[] result) { if (y == null) { throw new ArgumentNullException(nameof(y)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (result == null) { throw new ArgumentNullException(nameof(result)); } if (y.Length != x.Length || y.Length != result.Length) { throw new ArgumentException("All vectors must have the same dimensionality."); } CommonParallel.For(0, y.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = x[i] * y[i]; } }); } /// /// Does a point wise division of two arrays z = x / y. This can be used /// to divide elements of vectors or matrices. /// /// The array x. /// The array y. /// The result of the point wise division. /// There is no equivalent BLAS routine, but many libraries /// provide optimized (parallel and/or vectorized) versions of this /// routine. public virtual void PointWiseDivideArrays(double[] x, double[] y, double[] result) { if (y == null) { throw new ArgumentNullException(nameof(y)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (result == null) { throw new ArgumentNullException(nameof(result)); } if (y.Length != x.Length || y.Length != result.Length) { throw new ArgumentException("All vectors must have the same dimensionality."); } CommonParallel.For(0, y.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = x[i] / y[i]; } }); } /// /// Does a point wise power of two arrays z = x ^ y. This can be used /// to raise elements of vectors or matrices to the powers of another vector or matrix. /// /// The array x. /// The array y. /// The result of the point wise power. /// There is no equivalent BLAS routine, but many libraries /// provide optimized (parallel and/or vectorized) versions of this /// routine. public virtual void PointWisePowerArrays(double[] x, double[] y, double[] result) { if (y == null) { throw new ArgumentNullException(nameof(y)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (result == null) { throw new ArgumentNullException(nameof(result)); } if (y.Length != x.Length || y.Length != result.Length) { throw new ArgumentException("All vectors must have the same dimensionality."); } CommonParallel.For(0, y.Length, 4096, (a, b) => { for (int i = a; i < b; i++) { result[i] = Math.Pow(x[i], y[i]); } }); } /// /// Computes the requested of the matrix. /// /// The type of norm to compute. /// The number of rows. /// The number of columns. /// The matrix to compute the norm from. /// /// The requested of the matrix. /// public virtual double MatrixNorm(Norm norm, int rows, int columns, double[] matrix) { switch (norm) { case Norm.OneNorm: var norm1 = 0d; for (var j = 0; j < columns; j++) { var s = 0.0; for (var i = 0; i < rows; i++) { s += Math.Abs(matrix[(j * rows) + i]); } norm1 = Math.Max(norm1, s); } return norm1; case Norm.LargestAbsoluteValue: var normMax = 0d; for (var j = 0; j < columns; j++) { for (var i = 0; i < rows; i++) { normMax = Math.Max(Math.Abs(matrix[(j * rows) + i]), normMax); } } return normMax; case Norm.InfinityNorm: var r = new double[rows]; for (var j = 0; j < columns; j++) { for (var i = 0; i < rows; i++) { r[i] += Math.Abs(matrix[(j * rows) + i]); } } // TODO: reuse var max = r[0]; for (int i = 0; i < r.Length; i++) { if (r[i] > max) { max = r[i]; } } return max; case Norm.FrobeniusNorm: var aat = new double[rows * rows]; MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.Transpose, 1.0, matrix, rows, columns, matrix, rows, columns, 0.0, aat); var normF = 0d; for (var i = 0; i < rows; i++) { normF += Math.Abs(aat[(i * rows) + i]); } return Math.Sqrt(normF); default: throw new NotSupportedException(); } } /// /// Multiples two matrices. result = x * y /// /// The x matrix. /// The number of rows in the x matrix. /// The number of columns in the x matrix. /// The y matrix. /// The number of rows in the y matrix. /// The number of columns in the y matrix. /// Where to store the result of the multiplication. /// This is a simplified version of the BLAS GEMM routine with alpha /// set to 1.0 and beta set to 0.0, and x and y are not transposed. public virtual void MatrixMultiply(double[] x, int rowsX, int columnsX, double[] y, int rowsY, int columnsY, double[] result) { // First check some basic requirement on the parameters of the matrix multiplication. if (x == null) { throw new ArgumentNullException(nameof(x)); } if (y == null) { throw new ArgumentNullException(nameof(y)); } if (result == null) { throw new ArgumentNullException(nameof(result)); } if (rowsX * columnsX != x.Length) { throw new ArgumentException("x.Length != xRows * xColumns"); } if (rowsY * columnsY != y.Length) { throw new ArgumentException("y.Length != yRows * yColumns"); } if (columnsX != rowsY) { throw new ArgumentException("xColumns != yRows"); } if (rowsX * columnsY != result.Length) { throw new ArgumentException("xRows * yColumns != result.Length"); } // Check whether we will be overwriting any of our inputs and make copies if necessary. // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory // as result, we can do it on a row wise basis. We should investigate this. double[] xdata; if (ReferenceEquals(x, result)) { xdata = (double[])x.Clone(); } else { xdata = x; } double[] ydata; if (ReferenceEquals(y, result)) { ydata = (double[])y.Clone(); } else { ydata = y; } Array.Clear(result, 0, result.Length); CacheObliviousMatrixMultiply(Transpose.DontTranspose, Transpose.DontTranspose, 1.0, xdata, 0, 0, ydata, 0, 0, result, 0, 0, rowsX, columnsY, columnsX, rowsX, columnsY, columnsX, true); } /// /// Multiplies two matrices and updates another with the result. c = alpha*op(a)*op(b) + beta*c /// /// How to transpose the matrix. /// How to transpose the matrix. /// The value to scale matrix. /// The a matrix. /// The number of rows in the matrix. /// The number of columns in the matrix. /// The b matrix /// The number of rows in the matrix. /// The number of columns in the matrix. /// The value to scale the matrix. /// The c matrix. public virtual void MatrixMultiplyWithUpdate(Transpose transposeA, Transpose transposeB, double alpha, double[] a, int rowsA, int columnsA, double[] b, int rowsB, int columnsB, double beta, double[] c) { int m; // The number of rows of matrix op(A) and of the matrix C. int n; // The number of columns of matrix op(B) and of the matrix C. int k; // The number of columns of matrix op(A) and the rows of the matrix op(B). // First check some basic requirement on the parameters of the matrix multiplication. if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if ((int)transposeA > 111 && (int)transposeB > 111) { if (rowsA != columnsB) { throw new ArgumentOutOfRangeException(); } if (columnsA * rowsB != c.Length) { throw new ArgumentOutOfRangeException(); } m = columnsA; n = rowsB; k = rowsA; } else if ((int)transposeA > 111) { if (rowsA != rowsB) { throw new ArgumentOutOfRangeException(); } if (columnsA * columnsB != c.Length) { throw new ArgumentOutOfRangeException(); } m = columnsA; n = columnsB; k = rowsA; } else if ((int)transposeB > 111) { if (columnsA != columnsB) { throw new ArgumentOutOfRangeException(); } if (rowsA * rowsB != c.Length) { throw new ArgumentOutOfRangeException(); } m = rowsA; n = rowsB; k = columnsA; } else { if (columnsA != rowsB) { throw new ArgumentOutOfRangeException(); } if (rowsA * columnsB != c.Length) { throw new ArgumentOutOfRangeException(); } m = rowsA; n = columnsB; k = columnsA; } if (alpha == 0.0 && beta == 0.0) { Array.Clear(c, 0, c.Length); return; } // Check whether we will be overwriting any of our inputs and make copies if necessary. // TODO - we can don't have to allocate a completely new matrix when x or y point to the same memory // as result, we can do it on a row wise basis. We should investigate this. double[] adata; if (ReferenceEquals(a, c)) { adata = (double[])a.Clone(); } else { adata = a; } double[] bdata; if (ReferenceEquals(b, c)) { bdata = (double[])b.Clone(); } else { bdata = b; } if (beta == 0.0) { Array.Clear(c, 0, c.Length); } else if (beta != 1.0) { ScaleArray(beta, c, c); } if (alpha == 0.0) { return; } CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, adata, 0, 0, bdata, 0, 0, c, 0, 0, m, n, k, m, n, k, true); } /// /// Cache-Oblivious Matrix Multiplication /// /// if set to true transpose matrix A. /// if set to true transpose matrix B. /// The value to scale the matrix A with. /// The matrix A. /// Row-shift of the left matrix /// Column-shift of the left matrix /// The matrix B. /// Row-shift of the right matrix /// Column-shift of the right matrix /// The matrix C. /// Row-shift of the result matrix /// Column-shift of the result matrix /// The number of rows of matrix op(A) and of the matrix C. /// The number of columns of matrix op(B) and of the matrix C. /// The number of columns of matrix op(A) and the rows of the matrix op(B). /// The constant number of rows of matrix op(A) and of the matrix C. /// The constant number of columns of matrix op(B) and of the matrix C. /// The constant number of columns of matrix op(A) and the rows of the matrix op(B). /// Indicates if this is the first recursion. static void CacheObliviousMatrixMultiply(Transpose transposeA, Transpose transposeB, double alpha, double[] matrixA, int shiftArow, int shiftAcol, double[] matrixB, int shiftBrow, int shiftBcol, double[] result, int shiftCrow, int shiftCcol, int m, int n, int k, int constM, int constN, int constK, bool first) { if (m + n <= Control.ParallelizeOrder || m == 1 || n == 1 || k == 1) { if ((int) transposeA > 111 && (int) transposeB > 111) { for (var m1 = 0; m1 < m; m1++) { var matArowPos = m1 + shiftArow; var matCrowPos = m1 + shiftCrow; for (var n1 = 0; n1 < n; ++n1) { var matBcolPos = n1 + shiftBcol; double sum = 0; for (var k1 = 0; k1 < k; ++k1) { sum += matrixA[(matArowPos*constK) + k1 + shiftAcol]* matrixB[((k1 + shiftBrow)*constN) + matBcolPos]; } result[((n1 + shiftCcol)*constM) + matCrowPos] += alpha*sum; } } } else if ((int) transposeA > 111) { for (var m1 = 0; m1 < m; m1++) { var matArowPos = m1 + shiftArow; var matCrowPos = m1 + shiftCrow; for (var n1 = 0; n1 < n; ++n1) { var matBcolPos = n1 + shiftBcol; double sum = 0; for (var k1 = 0; k1 < k; ++k1) { sum += matrixA[(matArowPos*constK) + k1 + shiftAcol]* matrixB[(matBcolPos*constK) + k1 + shiftBrow]; } result[((n1 + shiftCcol)*constM) + matCrowPos] += alpha*sum; } } } else if ((int) transposeB > 111) { for (var m1 = 0; m1 < m; m1++) { var matArowPos = m1 + shiftArow; var matCrowPos = m1 + shiftCrow; for (var n1 = 0; n1 < n; ++n1) { var matBcolPos = n1 + shiftBcol; double sum = 0; for (var k1 = 0; k1 < k; ++k1) { sum += matrixA[((k1 + shiftAcol)*constM) + matArowPos]* matrixB[((k1 + shiftBrow)*constN) + matBcolPos]; } result[((n1 + shiftCcol)*constM) + matCrowPos] += alpha*sum; } } } else { for (var m1 = 0; m1 < m; m1++) { var matArowPos = m1 + shiftArow; var matCrowPos = m1 + shiftCrow; for (var n1 = 0; n1 < n; ++n1) { var matBcolPos = n1 + shiftBcol; double sum = 0; for (var k1 = 0; k1 < k; ++k1) { sum += matrixA[((k1 + shiftAcol)*constM) + matArowPos]* matrixB[(matBcolPos*constK) + k1 + shiftBrow]; } result[((n1 + shiftCcol)*constM) + matCrowPos] += alpha*sum; } } } } else { // divide and conquer int m2 = m/2, n2 = n/2, k2 = k/2; if (first) { CommonParallel.Invoke( () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false), () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false), () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false), () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false)); CommonParallel.Invoke( () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false), () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false), () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false), () => CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false)); } else { CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k2, constM, constN, constK, false); CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k2, constM, constN, constK, false); CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow, shiftCcol, m2, n2, k - k2, constM, constN, constK, false); CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow, shiftCcol + n2, m2, n - n2, k - k2, constM, constN, constK, false); CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k2, constM, constN, constK, false); CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol, matrixB, shiftBrow, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k2, constM, constN, constK, false); CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol, result, shiftCrow + m2, shiftCcol, m - m2, n2, k - k2, constM, constN, constK, false); CacheObliviousMatrixMultiply(transposeA, transposeB, alpha, matrixA, shiftArow + m2, shiftAcol + k2, matrixB, shiftBrow + k2, shiftBcol + n2, result, shiftCrow + m2, shiftCcol + n2, m - m2, n - n2, k - k2, constM, constN, constK, false); } } } /// /// Computes the LUP factorization of A. P*A = L*U. /// /// An by matrix. The matrix is overwritten with the /// the LU factorization on exit. The lower triangular factor L is stored in under the diagonal of (the diagonal is always 1.0 /// for the L factor). The upper triangular factor U is stored on and above the diagonal of . /// The order of the square matrix . /// On exit, it contains the pivot indices. The size of the array must be . /// This is equivalent to the GETRF LAPACK routine. public virtual void LUFactor(double[] data, int order, int[] ipiv) { if (data == null) { throw new ArgumentNullException(nameof(data)); } if (ipiv == null) { throw new ArgumentNullException(nameof(ipiv)); } if (data.Length != order*order) { throw new ArgumentException("The array arguments must have the same length.", nameof(data)); } if (ipiv.Length != order) { throw new ArgumentException("The array arguments must have the same length.", nameof(ipiv)); } // Initialize the pivot matrix to the identity permutation. for (var i = 0; i < order; i++) { ipiv[i] = i; } var vecLUcolj = new double[order]; // Outer loop. for (var j = 0; j < order; j++) { var indexj = j*order; var indexjj = indexj + j; // Make a copy of the j-th column to localize references. for (var i = 0; i < order; i++) { vecLUcolj[i] = data[indexj + i]; } // Apply previous transformations. for (var i = 0; i < order; i++) { // Most of the time is spent in the following dot product. var kmax = Math.Min(i, j); var s = 0.0; for (var k = 0; k < kmax; k++) { s += data[(k*order) + i]*vecLUcolj[k]; } data[indexj + i] = vecLUcolj[i] -= s; } // Find pivot and exchange if necessary. var p = j; for (var i = j + 1; i < order; i++) { if (Math.Abs(vecLUcolj[i]) > Math.Abs(vecLUcolj[p])) { p = i; } } if (p != j) { for (var k = 0; k < order; k++) { var indexk = k*order; var indexkp = indexk + p; var indexkj = indexk + j; var temp = data[indexkp]; data[indexkp] = data[indexkj]; data[indexkj] = temp; } ipiv[j] = p; } // Compute multipliers. if (j < order & data[indexjj] != 0.0) { for (var i = j + 1; i < order; i++) { data[indexj + i] /= data[indexjj]; } } } } /// /// Computes the inverse of matrix using LU factorization. /// /// The N by N matrix to invert. Contains the inverse On exit. /// The order of the square matrix . /// This is equivalent to the GETRF and GETRI LAPACK routines. public virtual void LUInverse(double[] a, int order) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (a.Length != order*order) { throw new ArgumentException("The array arguments must have the same length.", nameof(a)); } var ipiv = new int[order]; LUFactor(a, order, ipiv); LUInverseFactored(a, order, ipiv); } /// /// Computes the inverse of a previously factored matrix. /// /// The LU factored N by N matrix. Contains the inverse On exit. /// The order of the square matrix . /// The pivot indices of . /// This is equivalent to the GETRI LAPACK routine. public virtual void LUInverseFactored(double[] a, int order, int[] ipiv) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (ipiv == null) { throw new ArgumentNullException(nameof(ipiv)); } if (a.Length != order*order) { throw new ArgumentException("The array arguments must have the same length.", nameof(a)); } if (ipiv.Length != order) { throw new ArgumentException("The array arguments must have the same length.", nameof(ipiv)); } var inverse = new double[a.Length]; for (var i = 0; i < order; i++) { inverse[i + (order*i)] = 1.0; } LUSolveFactored(order, a, order, ipiv, inverse); inverse.Copy(a); } /// /// Solves A*X=B for X using LU factorization. /// /// The number of columns of B. /// The square matrix A. /// The order of the square matrix . /// On entry the B matrix; on exit the X matrix. /// This is equivalent to the GETRF and GETRS LAPACK routines. public virtual void LUSolve(int columnsOfB, double[] a, int order, double[] b) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (a.Length != order*order) { throw new ArgumentException("The array arguments must have the same length.", nameof(a)); } if (b.Length != order*columnsOfB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } if (ReferenceEquals(a, b)) { throw new ArgumentException("Arguments must be different objects."); } var ipiv = new int[order]; var clone = new double[a.Length]; a.Copy(clone); LUFactor(clone, order, ipiv); LUSolveFactored(columnsOfB, clone, order, ipiv, b); } /// /// Solves A*X=B for X using a previously factored A matrix. /// /// The number of columns of B. /// The factored A matrix. /// The order of the square matrix . /// The pivot indices of . /// On entry the B matrix; on exit the X matrix. /// This is equivalent to the GETRS LAPACK routine. public virtual void LUSolveFactored(int columnsOfB, double[] a, int order, int[] ipiv, double[] b) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (ipiv == null) { throw new ArgumentNullException(nameof(ipiv)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (a.Length != order*order) { throw new ArgumentException("The array arguments must have the same length.", nameof(a)); } if (ipiv.Length != order) { throw new ArgumentException("The array arguments must have the same length.", nameof(ipiv)); } if (b.Length != order*columnsOfB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } if (ReferenceEquals(a, b)) { throw new ArgumentException("Arguments must be different objects."); } // Compute the column vector P*B for (var i = 0; i < ipiv.Length; i++) { if (ipiv[i] == i) { continue; } var p = ipiv[i]; for (var j = 0; j < columnsOfB; j++) { var indexk = j*order; var indexkp = indexk + p; var indexkj = indexk + i; var temp = b[indexkp]; b[indexkp] = b[indexkj]; b[indexkj] = temp; } } // Solve L*Y = P*B for (var k = 0; k < order; k++) { var korder = k*order; for (var i = k + 1; i < order; i++) { for (var j = 0; j < columnsOfB; j++) { var index = j*order; b[i + index] -= b[k + index]*a[i + korder]; } } } // Solve U*X = Y; for (var k = order - 1; k >= 0; k--) { var korder = k + (k*order); for (var j = 0; j < columnsOfB; j++) { b[k + (j*order)] /= a[korder]; } korder = k*order; for (var i = 0; i < k; i++) { for (var j = 0; j < columnsOfB; j++) { var index = j*order; b[i + index] -= b[k + index]*a[i + korder]; } } } } /// /// Computes the Cholesky factorization of A. /// /// On entry, a square, positive definite matrix. On exit, the matrix is overwritten with the /// the Cholesky factorization. /// The number of rows or columns in the matrix. /// This is equivalent to the POTRF LAPACK routine. public virtual void CholeskyFactor(double[] a, int order) { if (a == null) { throw new ArgumentNullException(nameof(a)); } var tmpColumn = new double[order]; // Main loop - along the diagonal for (int ij = 0; ij < order; ij++) { // "Pivot" element double tmpVal = a[(ij*order) + ij]; if (tmpVal > 0.0) { tmpVal = Math.Sqrt(tmpVal); a[(ij*order) + ij] = tmpVal; tmpColumn[ij] = tmpVal; // Calculate multipliers and copy to local column // Current column, below the diagonal for (int i = ij + 1; i < order; i++) { a[(ij*order) + i] /= tmpVal; tmpColumn[i] = a[(ij*order) + i]; } // Remaining columns, below the diagonal DoCholeskyStep(a, order, ij + 1, order, tmpColumn, Control.MaxDegreeOfParallelism); } else { throw new ArgumentException("Matrix must be positive definite."); } for (int i = ij + 1; i < order; i++) { a[(i*order) + ij] = 0.0; } } } /// /// Calculate Cholesky step /// /// Factor matrix /// Number of rows /// Column start /// Total columns /// Multipliers calculated previously /// Number of available processors static void DoCholeskyStep(double[] data, int rowDim, int firstCol, int colLimit, double[] multipliers, int availableCores) { var tmpColCount = colLimit - firstCol; if ((availableCores > 1) && (tmpColCount > Control.ParallelizeElements)) { var tmpSplit = firstCol + (tmpColCount/3); var tmpCores = availableCores/2; CommonParallel.Invoke( () => DoCholeskyStep(data, rowDim, firstCol, tmpSplit, multipliers, tmpCores), () => DoCholeskyStep(data, rowDim, tmpSplit, colLimit, multipliers, tmpCores)); } else { for (var j = firstCol; j < colLimit; j++) { var tmpVal = multipliers[j]; for (var i = j; i < rowDim; i++) { data[(j*rowDim) + i] -= multipliers[i]*tmpVal; } } } } /// /// Solves A*X=B for X using Cholesky factorization. /// /// The square, positive definite matrix A. /// The number of rows and columns in A. /// On entry the B matrix; on exit the X matrix. /// The number of columns in the B matrix. /// This is equivalent to the POTRF add POTRS LAPACK routines. public virtual void CholeskySolve(double[] a, int orderA, double[] b, int columnsB) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (b.Length != orderA*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } if (ReferenceEquals(a, b)) { throw new ArgumentException("Arguments must be different objects."); } var clone = new double[a.Length]; a.Copy(clone); CholeskyFactor(clone, orderA); CholeskySolveFactored(clone, orderA, b, columnsB); } /// /// Solves A*X=B for X using a previously factored A matrix. /// /// The square, positive definite matrix A. Has to be different than . /// The number of rows and columns in A. /// On entry the B matrix; on exit the X matrix. /// The number of columns in the B matrix. /// This is equivalent to the POTRS LAPACK routine. public virtual void CholeskySolveFactored(double[] a, int orderA, double[] b, int columnsB) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (b.Length != orderA*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } if (ReferenceEquals(a, b)) { throw new ArgumentException("Arguments must be different objects."); } CommonParallel.For(0, columnsB, (u, v) => { for (int i = u; i < v; i++) { DoCholeskySolve(a, orderA, b, i); } }); } /// /// Solves A*X=B for X using a previously factored A matrix. /// /// The square, positive definite matrix A. Has to be different than . /// The number of rows and columns in A. /// On entry the B matrix; on exit the X matrix. /// The column to solve for. static void DoCholeskySolve(double[] a, int orderA, double[] b, int index) { var cindex = index*orderA; // Solve L*Y = B; double sum; for (var i = 0; i < orderA; i++) { sum = b[cindex + i]; for (var k = i - 1; k >= 0; k--) { sum -= a[(k*orderA) + i]*b[cindex + k]; } b[cindex + i] = sum/a[(i*orderA) + i]; } // Solve L'*X = Y; for (var i = orderA - 1; i >= 0; i--) { sum = b[cindex + i]; var iindex = i*orderA; for (var k = i + 1; k < orderA; k++) { sum -= a[iindex + k]*b[cindex + k]; } b[cindex + i] = sum/a[iindex + i]; } } /// /// Computes the QR factorization of A. /// /// On entry, it is the M by N A matrix to factor. On exit, /// it is overwritten with the R matrix of the QR factorization. /// The number of rows in the A matrix. /// The number of columns in the A matrix. /// On exit, A M by M matrix that holds the Q matrix of the /// QR factorization. /// A min(m,n) vector. On exit, contains additional information /// to be used by the QR solve routine. /// This is similar to the GEQRF and ORGQR LAPACK routines. public virtual void QRFactor(double[] r, int rowsR, int columnsR, double[] q, double[] tau) { if (r == null) { throw new ArgumentNullException(nameof(r)); } if (q == null) { throw new ArgumentNullException(nameof(q)); } if (r.Length != rowsR*columnsR) { throw new ArgumentException("The given array has the wrong length. Should be rowsR * columnsR.", nameof(r)); } if (tau.Length < Math.Min(rowsR, columnsR)) { throw new ArgumentException("The given array is too small. It must be at least min(m,n) long.", nameof(tau)); } if (q.Length != rowsR*rowsR) { throw new ArgumentException("The given array has the wrong length. Should be rowsR * rowsR.", nameof(q)); } CommonParallel.For(0, rowsR, (a, b) => { for (var i = a; i < b; i++) { q[(i*rowsR) + i] = 1.0; } }); var work = columnsR > rowsR ? new double[rowsR * rowsR] : new double[rowsR * columnsR]; var minmn = Math.Min(rowsR, columnsR); for (var i = 0; i < minmn; i++) { GenerateColumn(work, r, rowsR, i, i); ComputeQR(work, i, r, i, rowsR, i + 1, columnsR, Control.MaxDegreeOfParallelism); } for (var i = minmn - 1; i >= 0; i--) { ComputeQR(work, i, q, i, rowsR, i, rowsR, Control.MaxDegreeOfParallelism); } } /// /// Computes the QR factorization of A. /// /// On entry, it is the M by N A matrix to factor. On exit, /// it is overwritten with the Q matrix of the QR factorization. /// The number of rows in the A matrix. /// The number of columns in the A matrix. /// On exit, A N by N matrix that holds the R matrix of the /// QR factorization. /// A min(m,n) vector. On exit, contains additional information /// to be used by the QR solve routine. /// This is similar to the GEQRF and ORGQR LAPACK routines. public virtual void ThinQRFactor(double[] a, int rowsA, int columnsA, double[] r, double[] tau) { if (r == null) { throw new ArgumentNullException(nameof(r)); } if (a == null) { throw new ArgumentNullException(nameof(a)); } if (a.Length != rowsA*columnsA) { throw new ArgumentException("The given array has the wrong length. Should be rowsR * columnsR.", nameof(a)); } if (tau.Length < Math.Min(rowsA, columnsA)) { throw new ArgumentException("The given array is too small. It must be at least min(m,n) long.", nameof(tau)); } if (r.Length != columnsA*columnsA) { throw new ArgumentException("The given array has the wrong length. Should be columnsA * columnsA.", nameof(r)); } var work = new double[rowsA*columnsA]; var minmn = Math.Min(rowsA, columnsA); for (var i = 0; i < minmn; i++) { GenerateColumn(work, a, rowsA, i, i); ComputeQR(work, i, a, i, rowsA, i + 1, columnsA, Control.MaxDegreeOfParallelism); } //copy R for (var j = 0; j < columnsA; j++) { var rIndex = j*columnsA; var aIndex = j*rowsA; for (var i = 0; i < columnsA; i++) { r[rIndex + i] = a[aIndex + i]; } } //clear A and set diagonals to 1 Array.Clear(a, 0, a.Length); for (var i = 0; i < columnsA; i++) { a[i*rowsA + i] = 1.0; } for (var i = minmn - 1; i >= 0; i--) { ComputeQR(work, i, a, i, rowsA, i, columnsA, Control.MaxDegreeOfParallelism); } } #region QR Factor Helper functions /// /// Perform calculation of Q or R /// /// Work array /// Index of column in work array /// Q or R matrices /// The first row in /// The last row /// The first column /// The last column /// Number of available CPUs static void ComputeQR(double[] work, int workIndex, double[] a, int rowStart, int rowCount, int columnStart, int columnCount, int availableCores) { if (rowStart > rowCount || columnStart > columnCount) { return; } var tmpColCount = columnCount - columnStart; if ((availableCores > 1) && (tmpColCount > 200)) { var tmpSplit = columnStart + (tmpColCount/2); var tmpCores = availableCores/2; CommonParallel.Invoke( () => ComputeQR(work, workIndex, a, rowStart, rowCount, columnStart, tmpSplit, tmpCores), () => ComputeQR(work, workIndex, a, rowStart, rowCount, tmpSplit, columnCount, tmpCores)); } else { for (var j = columnStart; j < columnCount; j++) { var scale = 0.0; for (var i = rowStart; i < rowCount; i++) { scale += work[(workIndex*rowCount) + i - rowStart]*a[(j*rowCount) + i]; } for (var i = rowStart; i < rowCount; i++) { a[(j*rowCount) + i] -= work[(workIndex*rowCount) + i - rowStart]*scale; } } } } /// /// Generate column from initial matrix to work array /// /// Work array /// Initial matrix /// The number of rows in matrix /// The first row /// Column index static void GenerateColumn(double[] work, double[] a, int rowCount, int row, int column) { var tmp = column*rowCount; var index = tmp + row; CommonParallel.For(row, rowCount, (u, v) => { for (int i = u; i < v; i++) { var iIndex = tmp + i; work[iIndex - row] = a[iIndex]; a[iIndex] = 0.0; } }); var norm = 0.0; for (var i = 0; i < rowCount - row; ++i) { var iindex = tmp + i; norm += work[iindex]*work[iindex]; } norm = Math.Sqrt(norm); if (row == rowCount - 1 || norm == 0) { a[index] = -work[tmp]; work[tmp] = Constants.Sqrt2; return; } var scale = 1.0/norm; if (work[tmp] < 0.0) { scale *= -1.0; } a[index] = -1.0/scale; CommonParallel.For(0, rowCount - row, 4096, (u, v) => { for (int i = u; i < v; i++) { work[tmp + i] *= scale; } }); work[tmp] += 1.0; var s = Math.Sqrt(1.0/work[tmp]); CommonParallel.For(0, rowCount - row, 4096, (u, v) => { for (int i = u; i < v; i++) { work[tmp + i] *= s; } }); } #endregion /// /// Solves A*X=B for X using QR factorization of A. /// /// The A matrix. /// The number of rows in the A matrix. /// The number of columns in the A matrix. /// The B matrix. /// The number of columns of B. /// On exit, the solution matrix. /// The type of QR factorization to perform. /// Rows must be greater or equal to columns. public virtual void QRSolve(double[] a, int rows, int columns, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (a.Length != rows*columns) { throw new ArgumentException("The array arguments must have the same length.", nameof(a)); } if (b.Length != rows*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } if (x.Length != columns*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(x)); } if (rows < columns) { throw new ArgumentException("The number of rows must greater than or equal to the number of columns."); } var work = new double[rows * columns]; var clone = new double[a.Length]; a.Copy(clone); if (method == QRMethod.Full) { var q = new double[rows*rows]; QRFactor(clone, rows, columns, q, work); QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); } else { var r = new double[columns*columns]; ThinQRFactor(clone, rows, columns, r, work); QRSolveFactored(clone, r, rows, columns, null, b, columnsB, x, method); } } /// /// Solves A*X=B for X using a previously QR factored matrix. /// /// The Q matrix obtained by calling . /// The R matrix obtained by calling . /// The number of rows in the A matrix. /// The number of columns in the A matrix. /// Contains additional information on Q. Only used for the native solver /// and can be null for the managed provider. /// The B matrix. /// The number of columns of B. /// On exit, the solution matrix. /// The type of QR factorization to perform. /// Rows must be greater or equal to columns. public virtual void QRSolveFactored(double[] q, double[] r, int rowsA, int columnsA, double[] tau, double[] b, int columnsB, double[] x, QRMethod method = QRMethod.Full) { if (r == null) { throw new ArgumentNullException(nameof(r)); } if (q == null) { throw new ArgumentNullException(nameof(q)); } if (b == null) { throw new ArgumentNullException(nameof(q)); } if (x == null) { throw new ArgumentNullException(nameof(q)); } if (rowsA < columnsA) { throw new ArgumentException("The number of rows must greater than or equal to the number of columns."); } int rowsQ, columnsQ, rowsR, columnsR; if (method == QRMethod.Full) { rowsQ = columnsQ = rowsR = rowsA; columnsR = columnsA; } else { rowsQ = rowsA; columnsQ = rowsR = columnsR = columnsA; } if (r.Length != rowsR*columnsR) { throw new ArgumentException($"The given array has the wrong length. Should be {rowsR * columnsR}.", nameof(r)); } if (q.Length != rowsQ*columnsQ) { throw new ArgumentException($"The given array has the wrong length. Should be {rowsQ * columnsQ}.", nameof(q)); } if (b.Length != rowsA*columnsB) { throw new ArgumentException($"The given array has the wrong length. Should be {rowsA * columnsB}.", nameof(b)); } if (x.Length != columnsA*columnsB) { throw new ArgumentException($"The given array has the wrong length. Should be {columnsA * columnsB}.", nameof(x)); } var sol = new double[b.Length]; // Copy B matrix to "sol", so B data will not be changed Buffer.BlockCopy(b, 0, sol, 0, b.Length*Constants.SizeOfDouble); // Compute Y = transpose(Q)*B var column = new double[rowsA]; for (var j = 0; j < columnsB; j++) { var jm = j*rowsA; Array.Copy(sol, jm, column, 0, rowsA); CommonParallel.For(0, columnsA, (u, v) => { for (int i = u; i < v; i++) { var im = i*rowsA; var sum = 0.0; for (var k = 0; k < rowsA; k++) { sum += q[im + k]*column[k]; } sol[jm + i] = sum; } }); } // Solve R*X = Y; for (var k = columnsA - 1; k >= 0; k--) { var km = k*rowsR; for (var j = 0; j < columnsB; j++) { sol[(j*rowsA) + k] /= r[km + k]; } for (var i = 0; i < k; i++) { for (var j = 0; j < columnsB; j++) { var jm = j*rowsA; sol[jm + i] -= sol[jm + k]*r[km + i]; } } } // Fill result matrix for (var col = 0; col < columnsB; col++) { Array.Copy(sol, col*rowsA, x, col*columnsA, columnsR); } } /// /// Computes the singular value decomposition of A. /// /// Compute the singular U and VT vectors or not. /// On entry, the M by N matrix to decompose. On exit, A may be overwritten. /// The number of rows in the A matrix. /// The number of columns in the A matrix. /// The singular values of A in ascending value. /// If is true, on exit U contains the left /// singular vectors. /// If is true, on exit VT contains the transposed /// right singular vectors. /// This is equivalent to the GESVD LAPACK routine. public virtual void SingularValueDecomposition(bool computeVectors, double[] a, int rowsA, int columnsA, double[] s, double[] u, double[] vt) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (s == null) { throw new ArgumentNullException(nameof(s)); } if (u == null) { throw new ArgumentNullException(nameof(u)); } if (vt == null) { throw new ArgumentNullException(nameof(vt)); } if (u.Length != rowsA*rowsA) { throw new ArgumentException("The array arguments must have the same length.", nameof(u)); } if (vt.Length != columnsA*columnsA) { throw new ArgumentException("The array arguments must have the same length.", nameof(vt)); } if (s.Length != Math.Min(rowsA, columnsA)) { throw new ArgumentException("The array arguments must have the same length.", nameof(s)); } var work = new double[rowsA]; const int maxiter = 1000; var e = new double[columnsA]; var v = new double[vt.Length]; var stemp = new double[Math.Min(rowsA + 1, columnsA)]; int i, j, l, lp1; double t; var ncu = rowsA; // Reduce matrix to bidiagonal form, storing the diagonal elements // in "s" and the super-diagonal elements in "e". var nct = Math.Min(rowsA - 1, columnsA); var nrt = Math.Max(0, Math.Min(columnsA - 2, rowsA)); var lu = Math.Max(nct, nrt); for (l = 0; l < lu; l++) { lp1 = l + 1; if (l < nct) { // Compute the transformation for the l-th column and // place the l-th diagonal in vector s[l]. var sum = 0.0; for (var i1 = l; i1 < rowsA; i1++) { sum += a[(l*rowsA) + i1]*a[(l*rowsA) + i1]; } stemp[l] = Math.Sqrt(sum); if (stemp[l] != 0.0) { if (a[(l*rowsA) + l] != 0.0) { stemp[l] = Math.Abs(stemp[l])*(a[(l*rowsA) + l]/Math.Abs(a[(l*rowsA) + l])); } // A part of column "l" of Matrix A from row "l" to end multiply by 1.0 / s[l] for (i = l; i < rowsA; i++) { a[(l*rowsA) + i] = a[(l*rowsA) + i]*(1.0/stemp[l]); } a[(l*rowsA) + l] = 1.0 + a[(l*rowsA) + l]; } stemp[l] = -stemp[l]; } for (j = lp1; j < columnsA; j++) { if (l < nct) { if (stemp[l] != 0.0) { // Apply the transformation. t = 0.0; for (i = l; i < rowsA; i++) { t += a[(j*rowsA) + i]*a[(l*rowsA) + i]; } t = -t/a[(l*rowsA) + l]; for (var ii = l; ii < rowsA; ii++) { a[(j*rowsA) + ii] += t*a[(l*rowsA) + ii]; } } } // Place the l-th row of matrix into "e" for the // subsequent calculation of the row transformation. e[j] = a[(j*rowsA) + l]; } if (computeVectors && l < nct) { // Place the transformation in "u" for subsequent back multiplication. for (i = l; i < rowsA; i++) { u[(l*rowsA) + i] = a[(l*rowsA) + i]; } } if (l >= nrt) { continue; } // Compute the l-th row transformation and place the l-th super-diagonal in e(l). var enorm = 0.0; for (i = lp1; i < e.Length; i++) { enorm += e[i]*e[i]; } e[l] = Math.Sqrt(enorm); if (e[l] != 0.0) { if (e[lp1] != 0.0) { e[l] = Math.Abs(e[l])*(e[lp1]/Math.Abs(e[lp1])); } // Scale vector "e" from "lp1" by 1.0 / e[l] for (i = lp1; i < e.Length; i++) { e[i] = e[i]*(1.0/e[l]); } e[lp1] = 1.0 + e[lp1]; } e[l] = -e[l]; if (lp1 < rowsA && e[l] != 0.0) { // Apply the transformation. for (i = lp1; i < rowsA; i++) { work[i] = 0.0; } for (j = lp1; j < columnsA; j++) { for (var ii = lp1; ii < rowsA; ii++) { work[ii] += e[j]*a[(j*rowsA) + ii]; } } for (j = lp1; j < columnsA; j++) { var ww = -e[j]/e[lp1]; for (var ii = lp1; ii < rowsA; ii++) { a[(j*rowsA) + ii] += ww*work[ii]; } } } if (!computeVectors) { continue; } // Place the transformation in v for subsequent back multiplication. for (i = lp1; i < columnsA; i++) { v[(l*columnsA) + i] = e[i]; } } // Set up the final bidiagonal matrix or order m. var m = Math.Min(columnsA, rowsA + 1); var nctp1 = nct + 1; var nrtp1 = nrt + 1; if (nct < columnsA) { stemp[nctp1 - 1] = a[((nctp1 - 1)*rowsA) + (nctp1 - 1)]; } if (rowsA < m) { stemp[m - 1] = 0.0; } if (nrtp1 < m) { e[nrtp1 - 1] = a[((m - 1)*rowsA) + (nrtp1 - 1)]; } e[m - 1] = 0.0; // If required, generate "u". if (computeVectors) { for (j = nctp1 - 1; j < ncu; j++) { for (i = 0; i < rowsA; i++) { u[(j*rowsA) + i] = 0.0; } u[(j*rowsA) + j] = 1.0; } for (l = nct - 1; l >= 0; l--) { if (stemp[l] != 0.0) { for (j = l + 1; j < ncu; j++) { t = 0.0; for (i = l; i < rowsA; i++) { t += u[(j*rowsA) + i]*u[(l*rowsA) + i]; } t = -t/u[(l*rowsA) + l]; for (var ii = l; ii < rowsA; ii++) { u[(j*rowsA) + ii] += t*u[(l*rowsA) + ii]; } } // A part of column "l" of matrix A from row "l" to end multiply by -1.0 for (i = l; i < rowsA; i++) { u[(l*rowsA) + i] = u[(l*rowsA) + i]*-1.0; } u[(l*rowsA) + l] = 1.0 + u[(l*rowsA) + l]; for (i = 0; i < l; i++) { u[(l*rowsA) + i] = 0.0; } } else { for (i = 0; i < rowsA; i++) { u[(l*rowsA) + i] = 0.0; } u[(l*rowsA) + l] = 1.0; } } } // If it is required, generate v. if (computeVectors) { for (l = columnsA - 1; l >= 0; l--) { lp1 = l + 1; if (l < nrt) { if (e[l] != 0.0) { for (j = lp1; j < columnsA; j++) { t = 0.0; for (i = lp1; i < columnsA; i++) { t += v[(j*columnsA) + i]*v[(l*columnsA) + i]; } t = -t/v[(l*columnsA) + lp1]; for (var ii = l; ii < columnsA; ii++) { v[(j*columnsA) + ii] += t*v[(l*columnsA) + ii]; } } } } for (i = 0; i < columnsA; i++) { v[(l*columnsA) + i] = 0.0; } v[(l*columnsA) + l] = 1.0; } } // Transform "s" and "e" so that they are double for (i = 0; i < m; i++) { double r; if (stemp[i] != 0.0) { t = stemp[i]; r = stemp[i]/t; stemp[i] = t; if (i < m - 1) { e[i] = e[i]/r; } if (computeVectors) { // A part of column "i" of matrix U from row 0 to end multiply by r for (j = 0; j < rowsA; j++) { u[(i*rowsA) + j] = u[(i*rowsA) + j]*r; } } } // Exit if (i == m - 1) { break; } if (e[i] == 0.0) { continue; } t = e[i]; r = t/e[i]; e[i] = t; stemp[i + 1] = stemp[i + 1]*r; if (!computeVectors) { continue; } // A part of column "i+1" of matrix VT from row 0 to end multiply by r for (j = 0; j < columnsA; j++) { v[((i + 1)*columnsA) + j] = v[((i + 1)*columnsA) + j]*r; } } // Main iteration loop for the singular values. var mn = m; var iter = 0; while (m > 0) { // Quit if all the singular values have been found. // If too many iterations have been performed throw exception. if (iter >= maxiter) { throw new NonConvergenceException(); } // This section of the program inspects for negligible elements in the s and e arrays, // on completion the variables case and l are set as follows: // case = 1: if mS[m] and e[l-1] are negligible and l < m // case = 2: if mS[l] is negligible and l < m // case = 3: if e[l-1] is negligible, l < m, and mS[l, ..., mS[m] are not negligible (qr step). // case = 4: if e[m-1] is negligible (convergence). double ztest; double test; for (l = m - 2; l >= 0; l--) { test = Math.Abs(stemp[l]) + Math.Abs(stemp[l + 1]); ztest = test + Math.Abs(e[l]); if (ztest.AlmostEqualRelative(test, 15)) { e[l] = 0.0; break; } } int kase; if (l == m - 2) { kase = 4; } else { int ls; for (ls = m - 1; ls > l; ls--) { test = 0.0; if (ls != m - 1) { test = test + Math.Abs(e[ls]); } if (ls != l + 1) { test = test + Math.Abs(e[ls - 1]); } ztest = test + Math.Abs(stemp[ls]); if (ztest.AlmostEqualRelative(test, 15)) { stemp[ls] = 0.0; break; } } if (ls == l) { kase = 3; } else if (ls == m - 1) { kase = 1; } else { kase = 2; l = ls; } } l = l + 1; // Perform the task indicated by case. int k; double f; double cs; double sn; switch (kase) { // Deflate negligible s[m]. case 1: f = e[m - 2]; e[m - 2] = 0.0; double t1; for (var kk = l; kk < m - 1; kk++) { k = m - 2 - kk + l; t1 = stemp[k]; Drotg(ref t1, ref f, out cs, out sn); stemp[k] = t1; if (k != l) { f = -sn*e[k - 1]; e[k - 1] = cs*e[k - 1]; } if (computeVectors) { // Rotate for (i = 0; i < columnsA; i++) { var z = (cs*v[(k*columnsA) + i]) + (sn*v[((m - 1)*columnsA) + i]); v[((m - 1)*columnsA) + i] = (cs*v[((m - 1)*columnsA) + i]) - (sn*v[(k*columnsA) + i]); v[(k*columnsA) + i] = z; } } } break; // Split at negligible s[l]. case 2: f = e[l - 1]; e[l - 1] = 0.0; for (k = l; k < m; k++) { t1 = stemp[k]; Drotg(ref t1, ref f, out cs, out sn); stemp[k] = t1; f = -sn*e[k]; e[k] = cs*e[k]; if (computeVectors) { // Rotate for (i = 0; i < rowsA; i++) { var z = (cs*u[(k*rowsA) + i]) + (sn*u[((l - 1)*rowsA) + i]); u[((l - 1)*rowsA) + i] = (cs*u[((l - 1)*rowsA) + i]) - (sn*u[(k*rowsA) + i]); u[(k*rowsA) + i] = z; } } } break; // Perform one qr step. case 3: // calculate the shift. var scale = 0.0; scale = Math.Max(scale, Math.Abs(stemp[m - 1])); scale = Math.Max(scale, Math.Abs(stemp[m - 2])); scale = Math.Max(scale, Math.Abs(e[m - 2])); scale = Math.Max(scale, Math.Abs(stemp[l])); scale = Math.Max(scale, Math.Abs(e[l])); var sm = stemp[m - 1]/scale; var smm1 = stemp[m - 2]/scale; var emm1 = e[m - 2]/scale; var sl = stemp[l]/scale; var el = e[l]/scale; var b = (((smm1 + sm)*(smm1 - sm)) + (emm1*emm1))/2.0; var c = (sm*emm1)*(sm*emm1); var shift = 0.0; if (b != 0.0 || c != 0.0) { shift = Math.Sqrt((b*b) + c); if (b < 0.0) { shift = -shift; } shift = c/(b + shift); } f = ((sl + sm)*(sl - sm)) + shift; var g = sl*el; // Chase zeros for (k = l; k < m - 1; k++) { Drotg(ref f, ref g, out cs, out sn); if (k != l) { e[k - 1] = f; } f = (cs*stemp[k]) + (sn*e[k]); e[k] = (cs*e[k]) - (sn*stemp[k]); g = sn*stemp[k + 1]; stemp[k + 1] = cs*stemp[k + 1]; if (computeVectors) { for (i = 0; i < columnsA; i++) { var z = (cs*v[(k*columnsA) + i]) + (sn*v[((k + 1)*columnsA) + i]); v[((k + 1)*columnsA) + i] = (cs*v[((k + 1)*columnsA) + i]) - (sn*v[(k*columnsA) + i]); v[(k*columnsA) + i] = z; } } Drotg(ref f, ref g, out cs, out sn); stemp[k] = f; f = (cs*e[k]) + (sn*stemp[k + 1]); stemp[k + 1] = -(sn*e[k]) + (cs*stemp[k + 1]); g = sn*e[k + 1]; e[k + 1] = cs*e[k + 1]; if (computeVectors && k < rowsA) { for (i = 0; i < rowsA; i++) { var z = (cs*u[(k*rowsA) + i]) + (sn*u[((k + 1)*rowsA) + i]); u[((k + 1)*rowsA) + i] = (cs*u[((k + 1)*rowsA) + i]) - (sn*u[(k*rowsA) + i]); u[(k*rowsA) + i] = z; } } } e[m - 2] = f; iter = iter + 1; break; // Convergence case 4: // Make the singular value positive if (stemp[l] < 0.0) { stemp[l] = -stemp[l]; if (computeVectors) { // A part of column "l" of matrix VT from row 0 to end multiply by -1 for (i = 0; i < columnsA; i++) { v[(l*columnsA) + i] = v[(l*columnsA) + i]*-1.0; } } } // Order the singular value. while (l != mn - 1) { if (stemp[l] >= stemp[l + 1]) { break; } t = stemp[l]; stemp[l] = stemp[l + 1]; stemp[l + 1] = t; if (computeVectors && l < columnsA) { // Swap columns l, l + 1 for (i = 0; i < columnsA; i++) { var z = v[(l*columnsA) + i]; v[(l*columnsA) + i] = v[((l + 1)*columnsA) + i]; v[((l + 1)*columnsA) + i] = z; } } if (computeVectors && l < rowsA) { // Swap columns l, l + 1 for (i = 0; i < rowsA; i++) { var z = u[(l*rowsA) + i]; u[(l*rowsA) + i] = u[((l + 1)*rowsA) + i]; u[((l + 1)*rowsA) + i] = z; } } l = l + 1; } iter = 0; m = m - 1; break; } } if (computeVectors) { // Finally transpose "v" to get "vt" matrix for (i = 0; i < columnsA; i++) { for (j = 0; j < columnsA; j++) { vt[(j*columnsA) + i] = v[(i*columnsA) + j]; } } } // Copy stemp to s with size adjustment. We are using ported copy of linpack's svd code and it uses // a singular vector of length rows+1 when rows < columns. The last element is not used and needs to be removed. // We should port lapack's svd routine to remove this problem. Buffer.BlockCopy(stemp, 0, s, 0, Math.Min(rowsA, columnsA)*Constants.SizeOfDouble); } /// /// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s /// associated with the Givens rotation that zeros the y-coordinate of the point. /// /// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation /// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation /// Contains the parameter c associated with the Givens rotation /// Contains the parameter s associated with the Givens rotation /// This is equivalent to the DROTG LAPACK routine. static void Drotg(ref double da, ref double db, out double c, out double s) { double r, z; var roe = db; var absda = Math.Abs(da); var absdb = Math.Abs(db); if (absda > absdb) { roe = da; } var scale = absda + absdb; if (scale == 0.0) { c = 1.0; s = 0.0; r = 0.0; z = 0.0; } else { var sda = da/scale; var sdb = db/scale; r = scale*Math.Sqrt((sda*sda) + (sdb*sdb)); if (roe < 0.0) { r = -r; } c = da/r; s = db/r; z = 1.0; if (absda > absdb) { z = s; } if (absdb >= absda && c != 0.0) { z = 1.0/c; } } da = r; db = z; } /// /// Solves A*X=B for X using the singular value decomposition of A. /// /// On entry, the M by N matrix to decompose. /// The number of rows in the A matrix. /// The number of columns in the A matrix. /// The B matrix. /// The number of columns of B. /// On exit, the solution matrix. public virtual void SvdSolve(double[] a, int rowsA, int columnsA, double[] b, int columnsB, double[] x) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (b.Length != rowsA*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } if (x.Length != columnsA*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } var s = new double[Math.Min(rowsA, columnsA)]; var u = new double[rowsA*rowsA]; var vt = new double[columnsA*columnsA]; var clone = new double[a.Length]; Buffer.BlockCopy(a, 0, clone, 0, a.Length*Constants.SizeOfDouble); SingularValueDecomposition(true, clone, rowsA, columnsA, s, u, vt); SvdSolveFactored(rowsA, columnsA, s, u, vt, b, columnsB, x); } /// /// Solves A*X=B for X using a previously SVD decomposed matrix. /// /// The number of rows in the A matrix. /// The number of columns in the A matrix. /// The s values returned by . /// The left singular vectors returned by . /// The right singular vectors returned by . /// The B matrix. /// The number of columns of B. /// On exit, the solution matrix. public virtual void SvdSolveFactored(int rowsA, int columnsA, double[] s, double[] u, double[] vt, double[] b, int columnsB, double[] x) { if (s == null) { throw new ArgumentNullException(nameof(s)); } if (u == null) { throw new ArgumentNullException(nameof(u)); } if (vt == null) { throw new ArgumentNullException(nameof(vt)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (x == null) { throw new ArgumentNullException(nameof(x)); } if (u.Length != rowsA*rowsA) { throw new ArgumentException("The array arguments must have the same length.", nameof(u)); } if (vt.Length != columnsA*columnsA) { throw new ArgumentException("The array arguments must have the same length.", nameof(vt)); } if (s.Length != Math.Min(rowsA, columnsA)) { throw new ArgumentException("The array arguments must have the same length.", nameof(s)); } if (b.Length != rowsA*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } if (x.Length != columnsA*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(b)); } var mn = Math.Min(rowsA, columnsA); var tmp = new double[columnsA]; for (var k = 0; k < columnsB; k++) { for (var j = 0; j < columnsA; j++) { double value = 0; if (j < mn) { for (var i = 0; i < rowsA; i++) { value += u[(j*rowsA) + i]*b[(k*rowsA) + i]; } value /= s[j]; } tmp[j] = value; } for (var j = 0; j < columnsA; j++) { double value = 0; for (var i = 0; i < columnsA; i++) { value += vt[(j*columnsA) + i]*tmp[i]; } x[(k*columnsA) + j] = value; } } } /// /// Computes the eigenvalues and eigenvectors of a matrix. /// /// Whether the matrix is symmetric or not. /// The order of the matrix. /// The matrix to decompose. The length of the array must be order * order. /// On output, the matrix contains the eigen vectors. The length of the array must be order * order. /// On output, the eigen values (λ) of matrix in ascending value. The length of the array must . /// On output, the block diagonal eigenvalue matrix. The length of the array must be order * order. public virtual void EigenDecomp(bool isSymmetric, int order, double[] matrix, double[] matrixEv, Complex[] vectorEv, double[] matrixD) { if (matrix == null) { throw new ArgumentNullException(nameof(matrix)); } if (matrix.Length != order*order) { throw new ArgumentException($"The given array has the wrong length. Should be {order * order}.", nameof(matrix)); } if (matrixEv == null) { throw new ArgumentNullException(nameof(matrixEv)); } if (matrixEv.Length != order*order) { throw new ArgumentException($"The given array has the wrong length. Should be {order * order}.", nameof(matrixEv)); } if (vectorEv == null) { throw new ArgumentNullException(nameof(vectorEv)); } if (vectorEv.Length != order) { throw new ArgumentException($"The given array has the wrong length. Should be {order}.", nameof(vectorEv)); } if (matrixD == null) { throw new ArgumentNullException(nameof(matrixD)); } if (matrixD.Length != order*order) { throw new ArgumentException($"The given array has the wrong length. Should be {order * order}.", nameof(matrixD)); } var d = new double[order]; var e = new double[order]; if (isSymmetric) { Buffer.BlockCopy(matrix, 0, matrixEv, 0, matrix.Length*Constants.SizeOfDouble); var om1 = order - 1; for (var i = 0; i < order; i++) { d[i] = matrixEv[i*order + om1]; } Managed.ManagedLinearAlgebraProvider.SymmetricTridiagonalize(matrixEv, d, e, order); Managed.ManagedLinearAlgebraProvider.SymmetricDiagonalize(matrixEv, d, e, order); } else { var matrixH = new double[matrix.Length]; Buffer.BlockCopy(matrix, 0, matrixH, 0, matrix.Length*Constants.SizeOfDouble); Managed.ManagedLinearAlgebraProvider.NonsymmetricReduceToHessenberg(matrixEv, matrixH, order); Managed.ManagedLinearAlgebraProvider.NonsymmetricReduceHessenberToRealSchur(matrixEv, matrixH, d, e, order); } for (var i = 0; i < order; i++) { vectorEv[i] = new Complex(d[i], e[i]); var io = i*order; matrixD[io + i] = d[i]; if (e[i] > 0) { matrixD[io + order + i] = e[i]; matrixD[(i + 1)*order + i] = e[i]; } else if (e[i] < 0) { matrixD[io - order + i] = e[i]; } } } } }