// // 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; using static System.FormattableString; namespace IStation.Numerics.Providers.LinearAlgebra.Managed { /// /// The managed linear algebra provider. /// internal partial class ManagedLinearAlgebraProvider { /// /// 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(Complex[] y, Complex alpha, Complex[] x, Complex[] 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.IsZero()) { y.Copy(result); } else if (alpha.IsOne()) { for (int i = 0; i < result.Length; i++) { result[i] = y[i] + x[i]; } } else { for (int i = 0; i < result.Length; 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(Complex alpha, Complex[] x, Complex[] result) { if (x == null) { throw new ArgumentNullException(nameof(x)); } if (alpha.IsZero()) { Array.Clear(result, 0, result.Length); } else if (alpha.IsOne()) { x.Copy(result); } else { for (int i = 0; i < result.Length; 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(Complex[] x, Complex[] result) { if (x == null) { throw new ArgumentNullException(nameof(x)); } for (int i = 0; i < result.Length; i++) { result[i] = x[i].Conjugate(); } } /// /// 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 Complex DotProduct(Complex[] x, Complex[] 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."); } Complex dot = Complex.Zero; for (var index = 0; index < y.Length; index++) { dot += y[index]*x[index]; } return dot; } /// /// 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(Complex[] x, Complex[] y, Complex[] 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."); } for (int i = 0; i < result.Length; 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(Complex[] x, Complex[] y, Complex[] 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."); } for (int i = 0; i < result.Length; 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(Complex[] x, Complex[] y, Complex[] 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."); } for (int i = 0; i < result.Length; 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(Complex[] x, Complex[] y, Complex[] 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(Complex[] x, Complex[] y, Complex[] 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] = Complex.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, Complex[] 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 += matrix[(j*rows) + i].Magnitude; } 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(matrix[(j * rows) + i].Magnitude, 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] += matrix[(j * rows) + i].Magnitude; } } // 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 Complex[rows*rows]; MatrixMultiplyWithUpdate(Transpose.DontTranspose, Transpose.ConjugateTranspose, 1.0, matrix, rows, columns, matrix, rows, columns, 0.0, aat); var normF = 0d; for (var i = 0; i < rows; i++) { normF += aat[(i * rows) + i].Magnitude; } 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(Complex[] x, int rowsX, int columnsX, Complex[] y, int rowsY, int columnsY, Complex[] result) { 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 (columnsX != rowsY) { throw new ArgumentOutOfRangeException(Invariant($"columnsA ({columnsX}) != rowsB ({rowsY})")); } if (rowsX * columnsX != x.Length) { throw new ArgumentOutOfRangeException(Invariant($"rowsA ({rowsX}) * columnsA ({columnsX}) != a.Length ({x.Length})")); } if (rowsY * columnsY != y.Length) { throw new ArgumentOutOfRangeException(Invariant($"rowsB ({rowsY}) * columnsB ({columnsY}) != b.Length ({y.Length})")); } if (rowsX * columnsY != result.Length) { throw new ArgumentOutOfRangeException(Invariant($"rowsA ({rowsX}) * columnsB ({columnsY}) != c.Length ({result.Length})")); } // handle degenerate cases Array.Clear(result, 0, result.Length); // Extract column arrays var columnDataB = new Complex[columnsY][]; for (int i = 0; i < columnDataB.Length; i++) { var column = new Complex[rowsY]; GetColumn(Transpose.DontTranspose, i, rowsY, columnsY, y, column); columnDataB[i] = column; } var shouldNotParallelize = rowsX + columnsY + columnsX < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; if (shouldNotParallelize) { var row = new Complex[columnsX]; for (int i = 0; i < rowsX; i++) { GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); for (int j = 0; j < columnsY; j++) { var col = columnDataB[j]; Complex sum = Complex.Zero; for (int ii = 0; ii < row.Length; ii++) { sum += row[ii] * col[ii]; } result[j * rowsX + i] += Complex.One * sum; } } } else { CommonParallel.For(0, rowsX, 1, (u, v) => { var row = new Complex[columnsX]; for (int i = u; i < v; i++) { GetRow(Transpose.DontTranspose, i, rowsX, columnsX, x, row); for (int j = 0; j < columnsY; j++) { var column = columnDataB[j]; Complex sum = Complex.Zero; for (int ii = 0; ii < row.Length; ii++) { sum += row[ii] * column[ii]; } result[j * rowsX + i] += Complex.One * sum; } } }); } } /// /// 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, Complex alpha, Complex[] a, int rowsA, int columnsA, Complex[] b, int rowsB, int columnsB, Complex beta, Complex[] c) { if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } if (c == null) { throw new ArgumentNullException(nameof(c)); } if (transposeA != Transpose.DontTranspose) { var swap = rowsA; rowsA = columnsA; columnsA = swap; } if (transposeB != Transpose.DontTranspose) { var swap = rowsB; rowsB = columnsB; columnsB = swap; } if (columnsA != rowsB) { throw new ArgumentOutOfRangeException(Invariant($"columnsA ({columnsA}) != rowsB ({rowsB})")); } if (rowsA * columnsA != a.Length) { throw new ArgumentOutOfRangeException(Invariant($"rowsA ({rowsA}) * columnsA ({columnsA}) != a.Length ({a.Length})")); } if (rowsB * columnsB != b.Length) { throw new ArgumentOutOfRangeException(Invariant($"rowsB ({rowsB}) * columnsB ({columnsB}) != b.Length ({b.Length})")); } if (rowsA * columnsB != c.Length) { throw new ArgumentOutOfRangeException(Invariant($"rowsA ({rowsA}) * columnsB ({columnsB}) != c.Length ({c.Length})")); } // handle degenerate cases if (beta == Complex.Zero) { Array.Clear(c, 0, c.Length); } else if (beta != Complex.One) { ScaleArray(beta, c, c); } if (alpha == Complex.Zero) { return; } // Extract column arrays var columnDataB = new Complex[columnsB][]; for (int i = 0; i < columnDataB.Length; i++) { var column = new Complex[rowsB]; GetColumn(transposeB, i, rowsB, columnsB, b, column); columnDataB[i] = column; } var shouldNotParallelize = rowsA + columnsB + columnsA < Control.ParallelizeOrder || Control.MaxDegreeOfParallelism < 2; if (shouldNotParallelize) { var row = new Complex[columnsA]; for (int i = 0; i < rowsA; i++) { GetRow(transposeA, i, rowsA, columnsA, a, row); for (int j = 0; j < columnsB; j++) { var col = columnDataB[j]; Complex sum = Complex.Zero; for (int ii = 0; ii < row.Length; ii++) { sum += row[ii] * col[ii]; } c[j * rowsA + i] += alpha * sum; } } } else { CommonParallel.For(0, rowsA, 1, (u, v) => { var row = new Complex[columnsA]; for (int i = u; i < v; i++) { GetRow(transposeA, i, rowsA, columnsA, a, row); for (int j = 0; j < columnsB; j++) { var column = columnDataB[j]; Complex sum = Complex.Zero; for (int ii = 0; ii < row.Length; ii++) { sum += row[ii] * column[ii]; } c[j * rowsA + i] += alpha * sum; } } }); } } /// /// 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(Complex[] 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 Complex[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 = Complex.Zero; 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 (vecLUcolj[i].Magnitude > vecLUcolj[p].Magnitude) { 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(Complex[] 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(Complex[] 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 Complex[a.Length]; for (var i = 0; i < order; i++) { inverse[i + (order*i)] = Complex.One; } 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, Complex[] a, int order, Complex[] 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 Complex[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, Complex[] a, int order, int[] ipiv, Complex[] 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(Complex[] a, int order) { if (a == null) { throw new ArgumentNullException(nameof(a)); } var tmpColumn = new Complex[order]; // Main loop - along the diagonal for (var ij = 0; ij < order; ij++) { // "Pivot" element var tmpVal = a[(ij*order) + ij]; if (tmpVal.Real > 0.0) { tmpVal = tmpVal.SquareRoot(); a[(ij*order) + ij] = tmpVal; tmpColumn[ij] = tmpVal; // Calculate multipliers and copy to local column // Current column, below the diagonal for (var 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 (var 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(Complex[] data, int rowDim, int firstCol, int colLimit, Complex[] 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.Conjugate(); } } } } /// /// 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(Complex[] a, int orderA, Complex[] 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 Complex[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. /// The number of rows and columns in A. /// The B matrix. /// The number of columns in the B matrix. /// This is equivalent to the POTRS LAPACK routine. public virtual void CholeskySolveFactored(Complex[] a, int orderA, Complex[] 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(Complex[] a, int orderA, Complex[] b, int index) { var cindex = index*orderA; // Solve L*Y = B; Complex 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].Conjugate()*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(Complex[] r, int rowsR, int columnsR, Complex[] q, Complex[] 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)); } var work = columnsR > rowsR ? new Complex[rowsR*rowsR] : new Complex[rowsR*columnsR]; CommonParallel.For(0, rowsR, (a, b) => { for (int i = a; i < b; i++) { q[(i*rowsR) + i] = Complex.One; } }); 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(Complex[] a, int rowsA, int columnsA, Complex[] r, Complex[] 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 Complex[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] = Complex.One; } 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(Complex[] work, int workIndex, Complex[] 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 = Complex.Zero; 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].Conjugate()*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(Complex[] work, Complex[] 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] = Complex.Zero; } }); var norm = Complex.Zero; for (var i = 0; i < rowCount - row; ++i) { var index1 = tmp + i; norm += work[index1].Magnitude*work[index1].Magnitude; } norm = norm.SquareRoot(); if (row == rowCount - 1 || norm.Magnitude == 0) { a[index] = -work[tmp]; work[tmp] = new Complex(2.0, 0).SquareRoot(); return; } if (work[tmp].Magnitude != 0.0) { norm = norm.Magnitude*(work[tmp]/work[tmp].Magnitude); } a[index] = -norm; CommonParallel.For(0, rowCount - row, 4096, (u, v) => { for (int i = u; i < v; i++) { work[tmp + i] /= norm; } }); work[tmp] += 1.0; var s = (1.0/work[tmp]).SquareRoot(); CommonParallel.For(0, rowCount - row, 4096, (u, v) => { for (int i = u; i < v; i++) { work[tmp + i] = work[tmp + i].Conjugate()*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(Complex[] a, int rows, int columns, Complex[] b, int columnsB, Complex[] 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 (rows < columns) { throw new ArgumentException("The number of rows must greater than or equal to the number of columns."); } if (x.Length != columns*columnsB) { throw new ArgumentException("The array arguments must have the same length.", nameof(x)); } var work = new Complex[rows * columns]; var clone = new Complex[a.Length]; a.Copy(clone); if (method == QRMethod.Full) { var q = new Complex[rows*rows]; QRFactor(clone, rows, columns, q, work); QRSolveFactored(q, clone, rows, columns, null, b, columnsB, x, method); } else { var r = new Complex[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(Complex[] q, Complex[] r, int rowsA, int columnsA, Complex[] tau, Complex[] b, int columnsB, Complex[] 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 Complex[b.Length]; // Copy B matrix to "sol", so B data will not be changed Array.Copy(b, 0, sol, 0, b.Length); // Compute Y = transpose(Q)*B var column = new Complex[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 = Complex.Zero; for (var k = 0; k < rowsA; k++) { sum += q[im + k].Conjugate()*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, Complex[] a, int rowsA, int columnsA, Complex[] s, Complex[] u, Complex[] 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 Complex[rowsA]; const int maxiter = 1000; var e = new Complex[columnsA]; var v = new Complex[vt.Length]; var stemp = new Complex[Math.Min(rowsA + 1, columnsA)]; int i, j, l, lp1; Complex 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 (i = l; i < rowsA; i++) { sum += a[(l*rowsA) + i].Magnitude*a[(l*rowsA) + i].Magnitude; } stemp[l] = Math.Sqrt(sum); if (stemp[l] != 0.0) { if (a[(l*rowsA) + l] != 0.0) { stemp[l] = stemp[l].Magnitude*(a[(l*rowsA) + l]/a[(l*rowsA) + l].Magnitude); } // 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[(l*rowsA) + i].Conjugate()*a[(j*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].Conjugate(); } 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].Magnitude*e[i].Magnitude; } e[l] = Math.Sqrt(enorm); if (e[l] != 0.0) { if (e[lp1] != 0.0) { e[l] = e[l].Magnitude*(e[lp1]/e[lp1].Magnitude); } // 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].Conjugate(); 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]).Conjugate(); 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[(l*rowsA) + i].Conjugate()*u[(j*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[(l*columnsA) + i].Conjugate()*v[(j*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++) { Complex r; if (stemp[i] != 0.0) { t = stemp[i].Magnitude; 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].Magnitude; 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 = stemp[l].Magnitude + stemp[l + 1].Magnitude; ztest = test + e[l].Magnitude; 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 + e[ls].Magnitude; } if (ls != l + 1) { test = test + e[ls - 1].Magnitude; } ztest = test + stemp[ls].Magnitude; 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].Real; e[m - 2] = 0.0; double t1; for (var kk = l; kk < m - 1; kk++) { k = m - 2 - kk + l; t1 = stemp[k].Real; Drotg(ref t1, ref f, out cs, out sn); stemp[k] = t1; if (k != l) { f = -sn*e[k - 1].Real; 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].Real; e[l - 1] = 0.0; for (k = l; k < m; k++) { t1 = stemp[k].Real; Drotg(ref t1, ref f, out cs, out sn); stemp[k] = t1; f = -sn*e[k].Real; 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, stemp[m - 1].Magnitude); scale = Math.Max(scale, stemp[m - 2].Magnitude); scale = Math.Max(scale, e[m - 2].Magnitude); scale = Math.Max(scale, stemp[l].Magnitude); scale = Math.Max(scale, e[l].Magnitude); var sm = stemp[m - 1].Real/scale; var smm1 = stemp[m - 2].Real/scale; var emm1 = e[m - 2].Real/scale; var sl = stemp[l].Real/scale; var el = e[l].Real/scale; var b = (((smm1 + sm)*(smm1 - sm)) + (emm1*emm1))/2.0; var c = (sm*emm1)*(sm*emm1); var shift = 0.0; if (b != 0.0 || c != 0.0) { shift = Math.Sqrt((b*b) + c); if (b < 0.0) { shift = -shift; } shift = c/(b + shift); } f = ((sl + sm)*(sl - sm)) + shift; var g = sl*el; // Chase zeros for (k = l; k < m - 1; k++) { Drotg(ref f, ref g, out cs, out sn); if (k != l) { e[k - 1] = f; } f = (cs*stemp[k].Real) + (sn*e[k].Real); e[k] = (cs*e[k]) - (sn*stemp[k]); g = sn*stemp[k + 1].Real; 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].Real) + (sn*stemp[k + 1].Real); stemp[k + 1] = -(sn*e[k]) + (cs*stemp[k + 1]); g = sn*e[k + 1].Real; 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].Real < 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].Real >= stemp[l + 1].Real) { 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].Conjugate(); } } } // 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. Array.Copy(stemp, 0, s, 0, Math.Min(rowsA, columnsA)); } /// /// 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(Complex[] a, int rowsA, int columnsA, Complex[] b, int columnsB, Complex[] 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 Complex[Math.Min(rowsA, columnsA)]; var u = new Complex[rowsA*rowsA]; var vt = new Complex[columnsA*columnsA]; var clone = new Complex[a.Length]; a.Copy(clone); 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, Complex[] s, Complex[] u, Complex[] vt, Complex[] b, int columnsB, Complex[] 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 Complex[columnsA]; for (var k = 0; k < columnsB; k++) { for (var j = 0; j < columnsA; j++) { var value = Complex.Zero; if (j < mn) { for (var i = 0; i < rowsA; i++) { value += u[(j*rowsA) + i].Conjugate()*b[(k*rowsA) + i]; } value /= s[j]; } tmp[j] = value; } for (var j = 0; j < columnsA; j++) { var value = Complex.Zero; for (var i = 0; i < columnsA; i++) { value += vt[(j*columnsA) + i].Conjugate()*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, Complex[] matrix, Complex[] matrixEv, Complex[] vectorEv, Complex[] 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 matrixCopy = new Complex[matrix.Length]; Array.Copy(matrix, 0, matrixCopy, 0, matrix.Length); if (isSymmetric) { var tau = new Complex[order]; var d = new double[order]; var e = new double[order]; SymmetricTridiagonalize(matrixCopy, d, e, tau, order); SymmetricDiagonalize(matrixEv, d, e, order); SymmetricUntridiagonalize(matrixEv, matrixCopy, tau, order); for (var i = 0; i < order; i++) { vectorEv[i] = new Complex(d[i], e[i]); } } else { NonsymmetricReduceToHessenberg(matrixEv, matrixCopy, order); NonsymmetricReduceHessenberToRealSchur(vectorEv, matrixEv, matrixCopy, order); } for (var i = 0; i < order; i++) { matrixD[i*order + i] = vectorEv[i]; } } /// /// Reduces a complex Hermitian matrix to a real symmetric tridiagonal matrix using unitary similarity transformations. /// /// Source matrix to reduce /// Output: Arrays for internal storage of real parts of eigenvalues /// Output: Arrays for internal storage of imaginary parts of eigenvalues /// Output: Arrays that contains further information about the transformations. /// Order of initial matrix /// This is derived from the Algol procedures HTRIDI by /// Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. internal static void SymmetricTridiagonalize(Complex[] matrixA, double[] d, double[] e, Complex[] tau, int order) { double hh; tau[order - 1] = Complex.One; for (var i = 0; i < order; i++) { d[i] = matrixA[i*order + i].Real; } // Householder reduction to tridiagonal form. for (var i = order - 1; i > 0; i--) { // Scale to avoid under/overflow. var scale = 0.0; var h = 0.0; for (var k = 0; k < i; k++) { scale = scale + Math.Abs(matrixA[k*order + i].Real) + Math.Abs(matrixA[k*order + i].Imaginary); } if (scale == 0.0) { tau[i - 1] = Complex.One; e[i] = 0.0; } else { for (var k = 0; k < i; k++) { matrixA[k*order + i] /= scale; h += matrixA[k*order + i].MagnitudeSquared(); } Complex g = Math.Sqrt(h); e[i] = scale*g.Real; Complex temp; var im1Oi = (i - 1)*order + i; var f = matrixA[im1Oi]; if (f.Magnitude != 0) { temp = -(matrixA[im1Oi].Conjugate()*tau[i].Conjugate())/f.Magnitude; h += f.Magnitude*g.Real; g = 1.0 + (g/f.Magnitude); matrixA[im1Oi] *= g; } else { temp = -tau[i].Conjugate(); matrixA[im1Oi] = g; } if ((f.Magnitude == 0) || (i != 1)) { f = Complex.Zero; for (var j = 0; j < i; j++) { var tmp = Complex.Zero; var jO = j*order; // Form element of A*U. for (var k = 0; k <= j; k++) { tmp += matrixA[k*order + j]*matrixA[k*order + i].Conjugate(); } for (var k = j + 1; k <= i - 1; k++) { tmp += matrixA[jO + k].Conjugate()*matrixA[k*order + i].Conjugate(); } // Form element of P tau[j] = tmp/h; f += (tmp/h)*matrixA[jO + i]; } hh = f.Real/(h + h); // Form the reduced A. for (var j = 0; j < i; j++) { f = matrixA[j*order + i].Conjugate(); g = tau[j] - (hh*f); tau[j] = g.Conjugate(); for (var k = 0; k <= j; k++) { matrixA[k*order + j] -= (f*tau[k]) + (g*matrixA[k*order + i]); } } } for (var k = 0; k < i; k++) { matrixA[k*order + i] *= scale; } tau[i - 1] = temp.Conjugate(); } hh = d[i]; d[i] = matrixA[i*order + i].Real; matrixA[i*order + i] = new Complex(hh, scale*Math.Sqrt(h)); } hh = d[0]; d[0] = matrixA[0].Real; matrixA[0] = hh; e[0] = 0.0; } /// /// Symmetric tridiagonal QL algorithm. /// /// Data array of matrix V (eigenvectors) /// Arrays for internal storage of real parts of eigenvalues /// Arrays for internal storage of imaginary parts of eigenvalues /// Order of initial matrix /// This is derived from the Algol procedures tql2, by /// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. /// internal static void SymmetricDiagonalize(Complex[] dataEv, double[] d, double[] e, int order) { const int maxiter = 1000; for (var i = 1; i < order; i++) { e[i - 1] = e[i]; } e[order - 1] = 0.0; var f = 0.0; var tst1 = 0.0; var eps = Precision.DoublePrecision; for (var l = 0; l < order; l++) { // Find small subdiagonal element tst1 = Math.Max(tst1, Math.Abs(d[l]) + Math.Abs(e[l])); var m = l; while (m < order) { if (Math.Abs(e[m]) <= eps*tst1) { break; } m++; } // If m == l, d[l] is an eigenvalue, // otherwise, iterate. if (m > l) { var iter = 0; do { iter = iter + 1; // (Could check iteration count here.) // Compute implicit shift var g = d[l]; var p = (d[l + 1] - g)/(2.0*e[l]); var r = SpecialFunctions.Hypotenuse(p, 1.0); if (p < 0) { r = -r; } d[l] = e[l]/(p + r); d[l + 1] = e[l]*(p + r); var dl1 = d[l + 1]; var h = g - d[l]; for (var i = l + 2; i < order; i++) { d[i] -= h; } f = f + h; // Implicit QL transformation. p = d[m]; var c = 1.0; var c2 = c; var c3 = c; var el1 = e[l + 1]; var s = 0.0; var s2 = 0.0; for (var i = m - 1; i >= l; i--) { c3 = c2; c2 = c; s2 = s; g = c*e[i]; h = c*p; r = SpecialFunctions.Hypotenuse(p, e[i]); e[i + 1] = s*r; s = e[i]/r; c = p/r; p = (c*d[i]) - (s*g); d[i + 1] = h + (s*((c*g) + (s*d[i]))); // Accumulate transformation. for (var k = 0; k < order; k++) { h = dataEv[((i + 1)*order) + k].Real; dataEv[((i + 1)*order) + k] = (s*dataEv[(i*order) + k].Real) + (c*h); dataEv[(i*order) + k] = (c*dataEv[(i*order) + k].Real) - (s*h); } } p = (-s)*s2*c3*el1*e[l]/dl1; e[l] = s*p; d[l] = c*p; // Check for convergence. If too many iterations have been performed, // throw exception that Convergence Failed if (iter >= maxiter) { throw new NonConvergenceException(); } } while (Math.Abs(e[l]) > eps*tst1); } d[l] = d[l] + f; e[l] = 0.0; } // Sort eigenvalues and corresponding vectors. for (var i = 0; i < order - 1; i++) { var k = i; var p = d[i]; for (var j = i + 1; j < order; j++) { if (d[j] < p) { k = j; p = d[j]; } } if (k != i) { d[k] = d[i]; d[i] = p; for (var j = 0; j < order; j++) { p = dataEv[(i*order) + j].Real; dataEv[(i*order) + j] = dataEv[(k*order) + j]; dataEv[(k*order) + j] = p; } } } } /// /// Determines eigenvectors by undoing the symmetric tridiagonalize transformation /// /// Data array of matrix V (eigenvectors) /// Previously tridiagonalized matrix by SymmetricTridiagonalize. /// Contains further information about the transformations /// Input matrix order /// This is derived from the Algol procedures HTRIBK, by /// by Smith, Boyle, Dongarra, Garbow, Ikebe, Klema, Moler, and Wilkinson, Handbook for /// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. internal static void SymmetricUntridiagonalize(Complex[] dataEv, Complex[] matrixA, Complex[] tau, int order) { for (var i = 0; i < order; i++) { for (var j = 0; j < order; j++) { dataEv[(j*order) + i] = dataEv[(j*order) + i].Real*tau[i].Conjugate(); } } // Recover and apply the Householder matrices. for (var i = 1; i < order; i++) { var h = matrixA[i*order + i].Imaginary; if (h != 0) { for (var j = 0; j < order; j++) { var s = Complex.Zero; for (var k = 0; k < i; k++) { s += dataEv[(j*order) + k]*matrixA[k*order + i]; } s = (s/h)/h; for (var k = 0; k < i; k++) { dataEv[(j*order) + k] -= s*matrixA[k*order + i].Conjugate(); } } } } } /// /// Nonsymmetric reduction to Hessenberg form. /// /// Data array of matrix V (eigenvectors) /// Array for internal storage of nonsymmetric Hessenberg form. /// Order of initial matrix /// This is derived from the Algol procedures orthes and ortran, /// by Martin and Wilkinson, Handbook for Auto. Comp., /// Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutines in EISPACK. internal static void NonsymmetricReduceToHessenberg(Complex[] dataEv, Complex[] matrixH, int order) { var ort = new Complex[order]; for (var m = 1; m < order - 1; m++) { // Scale column. var scale = 0.0; var mm1O = (m - 1)*order; for (var i = m; i < order; i++) { scale += Math.Abs(matrixH[mm1O + i].Real) + Math.Abs(matrixH[mm1O + i].Imaginary); } if (scale != 0.0) { // Compute Householder transformation. var h = 0.0; for (var i = order - 1; i >= m; i--) { ort[i] = matrixH[mm1O + i]/scale; h += ort[i].MagnitudeSquared(); } var g = Math.Sqrt(h); if (ort[m].Magnitude != 0) { h = h + (ort[m].Magnitude*g); g /= ort[m].Magnitude; ort[m] = (1.0 + g)*ort[m]; } else { ort[m] = g; matrixH[mm1O + m] = scale; } // Apply Householder similarity transformation // H = (I-u*u'/h)*H*(I-u*u')/h) for (var j = m; j < order; j++) { var f = Complex.Zero; var jO = j*order; for (var i = order - 1; i >= m; i--) { f += ort[i].Conjugate()*matrixH[jO + i]; } f = f/h; for (var i = m; i < order; i++) { matrixH[jO + i] -= f*ort[i]; } } for (var i = 0; i < order; i++) { var f = Complex.Zero; for (var j = order - 1; j >= m; j--) { f += ort[j]*matrixH[j*order + i]; } f = f/h; for (var j = m; j < order; j++) { matrixH[j*order + i] -= f*ort[j].Conjugate(); } } ort[m] = scale*ort[m]; matrixH[mm1O + m] *= -g; } } // Accumulate transformations (Algol's ortran). for (var i = 0; i < order; i++) { for (var j = 0; j < order; j++) { dataEv[(j*order) + i] = i == j ? Complex.One : Complex.Zero; } } for (var m = order - 2; m >= 1; m--) { var mm1O = (m - 1)*order; var mm1Om = mm1O + m; if (matrixH[mm1Om] != Complex.Zero && ort[m] != Complex.Zero) { var norm = (matrixH[mm1Om].Real*ort[m].Real) + (matrixH[mm1Om].Imaginary*ort[m].Imaginary); for (var i = m + 1; i < order; i++) { ort[i] = matrixH[mm1O + i]; } for (var j = m; j < order; j++) { var g = Complex.Zero; for (var i = m; i < order; i++) { g += ort[i].Conjugate()*dataEv[(j*order) + i]; } // Double division avoids possible underflow g /= norm; for (var i = m; i < order; i++) { dataEv[(j*order) + i] += g*ort[i]; } } } } // Create real subdiagonal elements. for (var i = 1; i < order; i++) { var im1 = i - 1; var im1O = im1*order; var im1Oi = im1O + i; var iO = i*order; if (matrixH[im1Oi].Imaginary != 0.0) { var y = matrixH[im1Oi]/matrixH[im1Oi].Magnitude; matrixH[im1Oi] = matrixH[im1Oi].Magnitude; for (var j = i; j < order; j++) { matrixH[j*order + i] *= y.Conjugate(); } for (var j = 0; j <= Math.Min(i + 1, order - 1); j++) { matrixH[iO + j] *= y; } for (var j = 0; j < order; j++) { dataEv[(i*order) + j] *= y; } } } } /// /// Nonsymmetric reduction from Hessenberg to real Schur form. /// /// Data array of the eigenvectors /// Data array of matrix V (eigenvectors) /// Array for internal storage of nonsymmetric Hessenberg form. /// Order of initial matrix /// This is derived from the Algol procedure hqr2, /// by Martin and Wilkinson, Handbook for Auto. Comp., /// Vol.ii-Linear Algebra, and the corresponding /// Fortran subroutine in EISPACK. internal static void NonsymmetricReduceHessenberToRealSchur(Complex[] vectorV, Complex[] dataEv, Complex[] matrixH, int order) { // Initialize var n = order - 1; var eps = Precision.DoublePrecision; double norm; Complex x, y, z, exshift = Complex.Zero; // Outer loop over eigenvalue index var iter = 0; while (n >= 0) { // Look for single small sub-diagonal element var l = n; while (l > 0) { var lm1 = l - 1; var lm1O = lm1*order; var lO = l*order; var tst1 = Math.Abs(matrixH[lm1O + lm1].Real) + Math.Abs(matrixH[lm1O + lm1].Imaginary) + Math.Abs(matrixH[lO + l].Real) + Math.Abs(matrixH[lO + l].Imaginary); if (Math.Abs(matrixH[lm1O + l].Real) < eps*tst1) { break; } l--; } var nm1 = n - 1; var nm1O = nm1*order; var nO = n*order; var nOn = nO + n; // Check for convergence // One root found if (l == n) { matrixH[nOn] += exshift; vectorV[n] = matrixH[nOn]; n--; iter = 0; } else { // Form shift Complex s; if (iter != 10 && iter != 20) { s = matrixH[nOn]; x = matrixH[nO + nm1]*matrixH[nm1O + n].Real; if (x.Real != 0.0 || x.Imaginary != 0.0) { y = (matrixH[nm1O + nm1] - s)/2.0; z = ((y*y) + x).SquareRoot(); if ((y.Real*z.Real) + (y.Imaginary*z.Imaginary) < 0.0) { z *= -1.0; } x /= y + z; s = s - x; } } else { // Form exceptional shift s = Math.Abs(matrixH[nm1O + n].Real) + Math.Abs(matrixH[(n - 2)*order + nm1].Real); } for (var i = 0; i <= n; i++) { matrixH[i*order + i] -= s; } exshift += s; iter++; // Reduce to triangle (rows) for (var i = l + 1; i <= n; i++) { var im1 = i - 1; var im1O = im1*order; var im1Oim1 = im1O + im1; s = matrixH[im1O + i].Real; norm = SpecialFunctions.Hypotenuse(matrixH[im1Oim1].Magnitude, s.Real); x = matrixH[im1Oim1]/norm; vectorV[i - 1] = x; matrixH[im1Oim1] = norm; matrixH[im1O + i] = new Complex(0.0, s.Real/norm); for (var j = i; j < order; j++) { var jO = j*order; y = matrixH[jO + im1]; z = matrixH[jO + i]; matrixH[jO + im1] = (x.Conjugate()*y) + (matrixH[im1O + i].Imaginary*z); matrixH[jO + i] = (x*z) - (matrixH[im1O + i].Imaginary*y); } } s = matrixH[nOn]; if (s.Imaginary != 0.0) { s /= matrixH[nOn].Magnitude; matrixH[nOn] = matrixH[nOn].Magnitude; for (var j = n + 1; j < order; j++) { matrixH[j*order + n] *= s.Conjugate(); } } // Inverse operation (columns). for (var j = l + 1; j <= n; j++) { x = vectorV[j - 1]; var jO = j*order; var jm1 = j - 1; var jm1O = jm1*order; var jm1Oj = jm1O + j; for (var i = 0; i <= j; i++) { var jm1Oi = jm1O + i; z = matrixH[jO + i]; if (i != j) { y = matrixH[jm1Oi]; matrixH[jm1Oi] = (x*y) + (matrixH[jm1O + j].Imaginary*z); } else { y = matrixH[jm1Oi].Real; matrixH[jm1Oi] = new Complex((x.Real*y.Real) - (x.Imaginary*y.Imaginary) + (matrixH[jm1O + j].Imaginary*z.Real), matrixH[jm1Oi].Imaginary); } matrixH[jO + i] = (x.Conjugate()*z) - (matrixH[jm1O + j].Imaginary*y); } for (var i = 0; i < order; i++) { y = dataEv[((j - 1)*order) + i]; z = dataEv[(j*order) + i]; dataEv[jm1O + i] = (x*y) + (matrixH[jm1Oj].Imaginary*z); dataEv[jO + i] = (x.Conjugate()*z) - (matrixH[jm1Oj].Imaginary*y); } } if (s.Imaginary != 0.0) { for (var i = 0; i <= n; i++) { matrixH[nO + i] *= s; } for (var i = 0; i < order; i++) { dataEv[nO + i] *= s; } } } } // All roots found. // Backsubstitute to find vectors of upper triangular form norm = 0.0; for (var i = 0; i < order; i++) { for (var j = i; j < order; j++) { norm = Math.Max(norm, Math.Abs(matrixH[j*order + i].Real) + Math.Abs(matrixH[j*order + i].Imaginary)); } } if (order == 1) { return; } if (norm == 0.0) { return; } for (n = order - 1; n > 0; n--) { var nO = n*order; var nOn = nO + n; x = vectorV[n]; matrixH[nOn] = 1.0; for (var i = n - 1; i >= 0; i--) { z = 0.0; for (var j = i + 1; j <= n; j++) { z += matrixH[j*order + i]*matrixH[nO + j]; } y = x - vectorV[i]; if (y.Real == 0.0 && y.Imaginary == 0.0) { y = eps*norm; } matrixH[nO + i] = z/y; // Overflow control var tr = Math.Abs(matrixH[nO + i].Real) + Math.Abs(matrixH[nO + i].Imaginary); if ((eps*tr)*tr > 1) { for (var j = i; j <= n; j++) { matrixH[nO + j] = matrixH[nO + j]/tr; } } } } // Back transformation to get eigenvectors of original matrix for (var j = order - 1; j > 0; j--) { var jO = j*order; for (var i = 0; i < order; i++) { z = Complex.Zero; for (var k = 0; k <= j; k++) { z += dataEv[(k*order) + i]*matrixH[jO + k]; } dataEv[jO + i] = z; } } } /// /// Assumes that and have already been transposed. /// protected static void GetRow(Transpose transpose, int rowindx, int numRows, int numCols, Complex[] matrix, Complex[] row) { if (transpose == Transpose.DontTranspose) { for (int i = 0; i < numCols; i++) { row[i] = matrix[(i * numRows) + rowindx]; } } else if (transpose == Transpose.ConjugateTranspose) { int offset = rowindx * numCols; for (int i = 0; i < row.Length; i++) { row[i] = matrix[i + offset].Conjugate(); } } else { Array.Copy(matrix, rowindx * numCols, row, 0, numCols); } } /// /// Assumes that and have already been transposed. /// protected static void GetColumn(Transpose transpose, int colindx, int numRows, int numCols, Complex[] matrix, Complex[] column) { if (transpose == Transpose.DontTranspose) { Array.Copy(matrix, colindx * numRows, column, 0, numRows); } else if (transpose == Transpose.ConjugateTranspose) { for (int i = 0; i < numRows; i++) { column[i] = matrix[(i * numCols) + colindx].Conjugate(); } } else { for (int i = 0; i < numRows; i++) { column[i] = matrix[(i * numCols) + colindx]; } } } } }