// Take a matrix and make all the eigenvalues non-negative private static Matrix projectToPositiveSemidefiniteMatrix(Matrix M) { int size = M.rows(); Utils.QL_REQUIRE(size == M.columns(), () => "matrix not square"); Matrix diagonal = new Matrix(size, size); SymmetricSchurDecomposition jd = new SymmetricSchurDecomposition(M); for (int i = 0; i < size; ++i) { diagonal[i, i] = Math.Max(jd.eigenvalues()[i], 0.0); } Matrix result = jd.eigenvectors() * diagonal * Matrix.transpose(jd.eigenvectors()); return(result); }
public static Matrix rankReducedSqrt(Matrix matrix, int maxRank, double componentRetainedPercentage, SalvagingAlgorithm sa) { int size = matrix.rows(); #if QL_EXTRA_SAFETY_CHECKS checkSymmetry(matrix); #else Utils.QL_REQUIRE(size == matrix.columns(), () => "non square matrix: " + size + " rows, " + matrix.columns() + " columns"); #endif Utils.QL_REQUIRE(componentRetainedPercentage > 0.0, () => "no eigenvalues retained"); Utils.QL_REQUIRE(componentRetainedPercentage <= 1.0, () => "percentage to be retained > 100%"); Utils.QL_REQUIRE(maxRank >= 1, () => "max rank required < 1"); // spectral (a.k.a Principal Component) analysis SymmetricSchurDecomposition jd = new SymmetricSchurDecomposition(matrix); Vector eigenValues = jd.eigenvalues(); // salvaging algorithm switch (sa) { case SalvagingAlgorithm.None: // eigenvalues are sorted in decreasing order Utils.QL_REQUIRE(eigenValues[size - 1] >= -1e-16, () => "negative eigenvalue(s) (" + eigenValues[size - 1] + ")"); break; case SalvagingAlgorithm.Spectral: // negative eigenvalues set to zero for (int i = 0; i < size; ++i) { eigenValues[i] = Math.Max(eigenValues[i], 0.0); } break; case SalvagingAlgorithm.Higham: { int maxIterations = 40; double tolerance = 1e-6; Matrix adjustedMatrix = highamImplementation(matrix, maxIterations, tolerance); jd = new SymmetricSchurDecomposition(adjustedMatrix); eigenValues = jd.eigenvalues(); } break; default: Utils.QL_FAIL("unknown or invalid salvaging algorithm"); break; } // factor reduction double accumulate = 0; eigenValues.ForEach((ii, vv) => accumulate += eigenValues[ii]); double enough = componentRetainedPercentage * accumulate; if (componentRetainedPercentage.IsEqual(1.0)) { // numerical glitches might cause some factors to be discarded enough *= 1.1; } // retain at least one factor double components = eigenValues[0]; int retainedFactors = 1; for (int i = 1; components < enough && i < size; ++i) { components += eigenValues[i]; retainedFactors++; } // output is granted to have a rank<=maxRank retainedFactors = Math.Min(retainedFactors, maxRank); Matrix diagonal = new Matrix(size, retainedFactors, 0.0); for (int i = 0; i < retainedFactors; ++i) { diagonal[i, i] = Math.Sqrt(eigenValues[i]); } Matrix result = jd.eigenvectors() * diagonal; normalizePseudoRoot(matrix, result); return(result); }
//! Returns the pseudo square root of a real symmetric matrix /*! Given a matrix \f$ M \f$, the result \f$ S \f$ is defined * as the matrix such that \f$ S S^T = M. \f$ * If the matrix is not positive semi definite, it can * return an approximation of the pseudo square root * using a (user selected) salvaging algorithm. * * For more information see: "The most general methodology to create * a valid correlation matrix for risk management and option pricing * purposes", by R. Rebonato and P. Jдckel. * The Journal of Risk, 2(2), Winter 1999/2000 * http://www.rebonato.com/correlationmatrix.pdf * * Revised and extended in "Monte Carlo Methods in Finance", * by Peter Jдckel, Chapter 6. * * \pre the given matrix must be symmetric. * * \relates Matrix * * \warning Higham algorithm only works for correlation matrices. * * \test * - the correctness of the results is tested by reproducing * known good data. * - the correctness of the results is tested by checking * returned values against numerical calculations. */ public static Matrix pseudoSqrt(Matrix matrix, SalvagingAlgorithm sa) { int size = matrix.rows(); #if QL_EXTRA_SAFETY_CHECKS checkSymmetry(matrix); #else Utils.QL_REQUIRE(size == matrix.columns(), () => "non square matrix: " + size + " rows, " + matrix.columns() + " columns"); #endif // spectral (a.k.a Principal Component) analysis SymmetricSchurDecomposition jd = new SymmetricSchurDecomposition(matrix); Matrix diagonal = new Matrix(size, size, 0.0); // salvaging algorithm Matrix result = new Matrix(size, size); bool negative; switch (sa) { case SalvagingAlgorithm.None: // eigenvalues are sorted in decreasing order Utils.QL_REQUIRE(jd.eigenvalues()[size - 1] >= -1e-16, () => "negative eigenvalue(s) (" + jd.eigenvalues()[size - 1] + ")"); result = MatrixUtilities.CholeskyDecomposition(matrix, true); break; case SalvagingAlgorithm.Spectral: // negative eigenvalues set to zero for (int i = 0; i < size; i++) { diagonal[i, i] = Math.Sqrt(Math.Max(jd.eigenvalues()[i], 0.0)); } result = jd.eigenvectors() * diagonal; normalizePseudoRoot(matrix, result); break; case SalvagingAlgorithm.Hypersphere: // negative eigenvalues set to zero negative = false; for (int i = 0; i < size; ++i) { diagonal[i, i] = Math.Sqrt(Math.Max(jd.eigenvalues()[i], 0.0)); if (jd.eigenvalues()[i] < 0.0) { negative = true; } } result = jd.eigenvectors() * diagonal; normalizePseudoRoot(matrix, result); if (negative) { result = hypersphereOptimize(matrix, result, false); } break; case SalvagingAlgorithm.LowerDiagonal: // negative eigenvalues set to zero negative = false; for (int i = 0; i < size; ++i) { diagonal[i, i] = Math.Sqrt(Math.Max(jd.eigenvalues()[i], 0.0)); if (jd.eigenvalues()[i] < 0.0) { negative = true; } } result = jd.eigenvectors() * diagonal; normalizePseudoRoot(matrix, result); if (negative) { result = hypersphereOptimize(matrix, result, true); } break; case SalvagingAlgorithm.Higham: int maxIterations = 40; double tol = 1e-6; result = highamImplementation(matrix, maxIterations, tol); result = MatrixUtilities.CholeskyDecomposition(result, true); break; default: Utils.QL_FAIL("unknown salvaging algorithm"); break; } return(result); }