DLLEXPORT lapack_int z_cholesky_factor(lapack_int n, lapack_complex_double a[]) { return cholesky_factor(n, a, LAPACK_zpotrf); }
DLLEXPORT lapack_int d_cholesky_factor(lapack_int n, double* a) { return cholesky_factor(n, a, LAPACK_dpotrf); }
DLLEXPORT lapack_int c_cholesky_factor(lapack_int n, lapack_complex_float a[]) { return cholesky_factor(n, a, LAPACK_cpotrf); }
DLLEXPORT MKL_INT z_cholesky_factor(MKL_INT n, MKL_Complex16 a[]) { return cholesky_factor(n, a, zpotrf); }
DLLEXPORT lapack_int s_cholesky_factor(lapack_int n, float a[]) { return cholesky_factor(n, a, LAPACK_spotrf); }
DLLEXPORT MKL_INT c_cholesky_factor(MKL_INT n, MKL_Complex8 a[]) { return cholesky_factor(n, a, cpotrf); }
DLLEXPORT MKL_INT d_cholesky_factor(MKL_INT n, double* a) { return cholesky_factor(n, a, dpotrf); }
DLLEXPORT MKL_INT s_cholesky_factor(MKL_INT n, float a[]) { return cholesky_factor(n, a, spotrf); }
/*!\rst Check that ``A * B`` works where ``A, B`` are matrices. Outline: 1. Simple hand-checked test case. 2. Generate a random orthogonal matrix and verify that ``Q * Q^T = I``. 3. Generate a random SPD matrix and guarantee good conditioning: verify ``A * A^-1 = I``. \return number of cases where matrix-matrix multiply failed \endrst*/ OL_WARN_UNUSED_RESULT int TestGeneralMatrixMatrixMultiply() noexcept { int total_errors = 0; // simple, hand-checked problems that are be minimally affected by floating point errors { // hide scope static const int kSize_m = 3; // rows of A, C static const int kSize_k = 5; // cols of A, rows of B static const int kSize_n = 2; // cols of B, C double matrix_A[kSize_m*kSize_k] = {-7.4, 0.1, 9.1, // first COLUMN of A (col-major storage) 1.5, -8.8, -0.3, -2.9, 6.4, -9.7, -9.1, -6.6, 3.1, 4.6, 3.0, -1.0 }; double matrix_B[kSize_k*kSize_n] = {-1.3, -8.1, -7.2, -0.4, -5.5, 7.4, 5.3, -3.1, -2.3, 1.9 }; double matrix_AB_exact[kSize_m*kSize_n] = {-3.309999999999995, 11.210000000000001, 64.700000000000003, -8.150000000000006, -44.860000000000014, 86.789999999999992 }; double matrix_AB_computed[kSize_m*kSize_n]; GeneralMatrixMatrixMultiply(matrix_A, 'N', matrix_B, 1.0, 0.0, kSize_m, kSize_k, kSize_n, matrix_AB_computed); for (int i = 0; i < kSize_m*kSize_n; ++i) { if (!CheckDoubleWithinRelative(matrix_AB_computed[i], matrix_AB_exact[i], 3.0 * std::numeric_limits<double>::epsilon())) { ++total_errors; } } } const int num_tests = 3; const int sizes[num_tests] = {3, 11, 20}; UniformRandomGenerator uniform_generator(34187); // in each iteration, we perform two tests on matrix-matrix multiply. // 1) form a matrix Q such that Q * Q^T = I (orthogonal matrix); nontrivial in that Q != I // Compute Q * Q^T and check the result. // 2) build a random, SPD matrix, A. (ill-conditioned). // Improve A's conditioning: A = A + size*I (condition number near 1 now) // Form A^-1 (this is OK because A is well-conditioned) // Check A * A^-1 is near I. for (int i = 0; i < num_tests; ++i) { std::vector<double> orthog_symm_matrix(sizes[i]*sizes[i]); std::vector<double> orthog_symm_matrix_T(sizes[i]*sizes[i]); std::vector<double> product_matrix(sizes[i]*sizes[i]); std::vector<double> spd_matrix(sizes[i]*sizes[i]); std::vector<double> cholesky_factor(sizes[i]*sizes[i]); std::vector<double> inverse_spd_matrix(sizes[i]*sizes[i]); std::vector<double> identity_matrix(sizes[i]*sizes[i]); BuildIdentityMatrix(sizes[i], identity_matrix.data()); BuildOrthogonalSymmetricMatrix(sizes[i], orthog_symm_matrix.data()); // not technically necessary since this orthog matrix is also symmetric MatrixTranspose(orthog_symm_matrix.data(), sizes[i], sizes[i], orthog_symm_matrix_T.data()); // Q * Q^T = I if Q is orthogonal GeneralMatrixMatrixMultiply(orthog_symm_matrix.data(), 'N', orthog_symm_matrix_T.data(), 1.0, 0.0, sizes[i], sizes[i], sizes[i], product_matrix.data()); VectorAXPY(sizes[i]*sizes[i], -1.0, identity_matrix.data(), product_matrix.data()); for (int j = 0; j < sizes[i]*sizes[i]; ++j) { // do not use relative comparison b/c we're testing against 0 if (!CheckDoubleWithin(product_matrix[j], 0.0, 20*std::numeric_limits<double>::epsilon())) { ++total_errors; } } // and again testing the T version GeneralMatrixMatrixMultiply(orthog_symm_matrix.data(), 'T', orthog_symm_matrix.data(), 1.0, 0.0, sizes[i], sizes[i], sizes[i], product_matrix.data()); VectorAXPY(sizes[i]*sizes[i], -1.0, identity_matrix.data(), product_matrix.data()); for (int j = 0; j < sizes[i]*sizes[i]; ++j) { // do not use relative comparison b/c we're testing against 0 if (!CheckDoubleWithin(product_matrix[j], 0.0, 20*std::numeric_limits<double>::epsilon())) { ++total_errors; } } BuildRandomSPDMatrix(sizes[i], &uniform_generator, spd_matrix.data()); // ensure spd matrix is well-conditioned (or we can't form A^-1 stably) ModifyMatrixDiagonal(sizes[i], static_cast<double>(sizes[i]), spd_matrix.data()); std::copy(spd_matrix.begin(), spd_matrix.end(), cholesky_factor.begin()); if (ComputeCholeskyFactorL(sizes[i], cholesky_factor.data()) != 0) { ++total_errors; } SPDMatrixInverse(cholesky_factor.data(), sizes[i], inverse_spd_matrix.data()); GeneralMatrixMatrixMultiply(spd_matrix.data(), 'N', inverse_spd_matrix.data(), 1.0, 0.0, sizes[i], sizes[i], sizes[i], product_matrix.data()); VectorAXPY(sizes[i]*sizes[i], -1.0, identity_matrix.data(), product_matrix.data()); for (int j = 0; j < sizes[i]*sizes[i]; ++j) { // do not use relative comparison b/c we're testing against 0 if (!CheckDoubleWithin(product_matrix[j], 0.0, 10*std::numeric_limits<double>::epsilon())) { ++total_errors; } } } return total_errors; }
/*!\rst Test that SPDMatrixInverse and CholeskyFactorLMatrixVectorSolve are working correctly against some especially bad named matrices and some random inputs. Outline: 1. Construct matrices, ``A`` 2. Select a solution, ``x``, randomly. 3. Construct RHS by doing ``A*x``. 4. Solve ``Ax = b`` using backsolve and direct-inverse; check the size of ``\|b - Ax\|``. \return number of test cases where the solver error is too large \endrst*/ OL_WARN_UNUSED_RESULT int TestSPDLinearSolvers() { int total_errors = 0; // simple/small case where numerical factors are not present. // taken from: http://en.wikipedia.org/wiki/Cholesky_decomposition#Example { constexpr int size = 3; std::vector<double> matrix = { 4.0, 12.0, -16.0, 12.0, 37.0, -43.0, -16.0, -43.0, 98.0}; const std::vector<double> cholesky_factor_L_truth = { 2.0, 6.0, -8.0, 0.0, 1.0, 5.0, 0.0, 0.0, 3.0}; std::vector<double> rhs = {-20.0, -43.0, 192.0}; std::vector<double> solution_truth = {1.0, 2.0, 3.0}; int local_errors = 0; // check factorization is correct; only check lower-triangle if (ComputeCholeskyFactorL(size, matrix.data()) != 0) { ++total_errors; } for (int i = 0; i < size; ++i) { for (int j = 0; j < size; ++j) { if (j >= i) { if (!CheckDoubleWithinRelative(matrix[i*size + j], cholesky_factor_L_truth[i*size + j], 0.0)) { ++local_errors; } } } } // check the solve is correct CholeskyFactorLMatrixVectorSolve(matrix.data(), size, rhs.data()); for (int i = 0; i < size; ++i) { if (!CheckDoubleWithinRelative(rhs[i], solution_truth[i], 0.0)) { ++local_errors; } } total_errors += local_errors; } const int num_tests = 10; const int num_test_sizes = 3; const int sizes[num_test_sizes] = {5, 11, 20}; // following tolerances are based on numerical experiments and/or computations // of the matrix condition numbers (in MATLAB) const double tolerance_backsolve_max_list[3][num_test_sizes] = { {10*std::numeric_limits<double>::epsilon(), 100*std::numeric_limits<double>::epsilon(), 100*std::numeric_limits<double>::epsilon()}, // prolate {100*std::numeric_limits<double>::epsilon(), 1.0e4*std::numeric_limits<double>::epsilon(), 1.0e6*std::numeric_limits<double>::epsilon()}, // moler {50*std::numeric_limits<double>::epsilon(), 1.0e3*std::numeric_limits<double>::epsilon(), 5.0e4*std::numeric_limits<double>::epsilon()} // random }; const double tolerance_inverse_max_list[3][num_test_sizes] = { {1.0e-13, 1.0e-9, 1.0e-2}, // prolate {5.0e-13, 2.0e-8, 1.0e1}, // moler {7.0e-10, 7.0e-6, 1.0e2} // random }; UniformRandomGenerator uniform_generator(34187); // In each iteration, we form some an ill-conditioned SPD matrix. We are using // prolate, moler, and random matrices. // Then we compute L * L^T = A. // We want to test solutions of A * x = b using two methods: // 1) Inverse: using L, we form A^-1 explicitly (ILL-CONDITIONED!) // 2) Backsolve: we never form A^-1, but instead backsolve L and L^T against b. // The resulting residual norms, ||b - A*x||_2 are computed; we check that // backsolve is accurate and inverse is affected strongly by conditioning. for (int j = 0; j < num_tests; ++j) { for (int i = 0; i < num_test_sizes; ++i) { std::vector<double> matrix(sizes[i]*sizes[i]); std::vector<double> inverse_matrix(sizes[i]*sizes[i]); std::vector<double> cholesky_factor(sizes[i]*sizes[i]); std::vector<double> rhs(sizes[i]); std::vector<double> solution(2*sizes[i]); double tolerance_backsolve_max; double tolerance_inverse_max; switch (j) { case 0: { BuildProlateMatrix(kProlateDefaultParameter, sizes[i], matrix.data()); tolerance_backsolve_max = tolerance_backsolve_max_list[0][i]; tolerance_inverse_max = tolerance_inverse_max_list[0][i]; break; } case 1: { BuildMolerMatrix(-1.66666666666, sizes[i], matrix.data()); tolerance_backsolve_max = tolerance_backsolve_max_list[1][i]; tolerance_inverse_max = tolerance_inverse_max_list[1][i]; break; } default: { if (j <= 1 || j > num_tests) { OL_THROW_EXCEPTION(BoundsException<int>, "Invalid switch option.", j, 2, num_tests); } else { BuildRandomSPDMatrix(sizes[i], &uniform_generator, matrix.data()); tolerance_backsolve_max = tolerance_backsolve_max_list[2][i]; tolerance_inverse_max = tolerance_inverse_max_list[2][i]; break; } } } // cholesky-factor A, form A^-1 std::copy(matrix.begin(), matrix.end(), cholesky_factor.begin()); if (ComputeCholeskyFactorL(sizes[i], cholesky_factor.data()) != 0) { ++total_errors; } SPDMatrixInverse(cholesky_factor.data(), sizes[i], inverse_matrix.data()); // set b = A*random_vector. This way we know the solution explicitly. // this also allows us to ignore ||A|| in our computations when we // normalize the RHS. BuildRandomVector(sizes[i], 0.0, 1.0, &uniform_generator, solution.data()); SymmetricMatrixVectorMultiply(matrix.data(), solution.data(), sizes[i], rhs.data()); // re-scale the RHS so that its norm can be ignored in later computations double rhs_norm = VectorNorm(rhs.data(), sizes[i]); VectorScale(sizes[i], 1.0/rhs_norm, rhs.data()); std::copy(rhs.begin(), rhs.end(), solution.begin()); // Solve L * L^T * x1 = b (backsolve) and compute x2 = A^-1 * b CholeskyFactorLMatrixVectorSolve(cholesky_factor.data(), sizes[i], solution.data()); SymmetricMatrixVectorMultiply(inverse_matrix.data(), rhs.data(), sizes[i], solution.data() + sizes[i]); double norm_residual_via_backsolve = ResidualNorm(matrix.data(), solution.data(), rhs.data(), sizes[i]); double norm_residual_via_inverse = ResidualNorm(matrix.data(), solution.data() + sizes[i], rhs.data(), sizes[i]); if (norm_residual_via_backsolve > tolerance_backsolve_max) { ++total_errors; OL_ERROR_PRINTF("experiment %d, size[%d] = %d, norm_backsolve = %.18E > %.18E = tol\n", j, i, sizes[i], norm_residual_via_backsolve, tolerance_backsolve_max); } if (norm_residual_via_inverse > tolerance_inverse_max) { ++total_errors; OL_ERROR_PRINTF("experiment %d, size[%d] = %d, norm_inverse = %.18E > %.18E = tol\n", j, i, sizes[i], norm_residual_via_inverse, tolerance_inverse_max); } } } return total_errors; }