From 357b9abaef117665bd2471c495846320aa8877cc Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Sat, 27 Apr 2024 13:33:13 +0200 Subject: [PATCH] Merge pull request #25450 from savuor:rv/svd_perf Perf tests for SVD and solve() created #25450 fixes #25336 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/core/perf/perf_math.cpp | 228 ++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/modules/core/perf/perf_math.cpp b/modules/core/perf/perf_math.cpp index 16d262e4c9..fe947aec1a 100644 --- a/modules/core/perf/perf_math.cpp +++ b/modules/core/perf/perf_math.cpp @@ -36,6 +36,234 @@ PERF_TEST_P(VectorLength, phase64f, testing::Values(128, 1000, 128*1024, 512*102 SANITY_CHECK(angle, 5e-5); } +// generates random vectors, performs Gram-Schmidt orthogonalization on them +Mat randomOrtho(int rows, int ftype, RNG& rng) +{ + Mat result(rows, rows, ftype); + rng.fill(result, RNG::UNIFORM, cv::Scalar(-1), cv::Scalar(1)); + + for (int i = 0; i < rows; i++) + { + Mat v = result.row(i); + + for (int j = 0; j < i; j++) + { + Mat p = result.row(j); + v -= p.dot(v) * p; + } + + v = v * (1. / cv::norm(v)); + } + + return result; +} + +template +Mat buildRandomMat(int rows, int cols, RNG& rng, int rank, bool symmetrical) +{ + int mtype = cv::traits::Depth::value; + Mat u = randomOrtho(rows, mtype, rng); + Mat v = randomOrtho(cols, mtype, rng); + Mat s(rows, cols, mtype, Scalar(0)); + + std::vector singVals(rank); + rng.fill(singVals, RNG::UNIFORM, Scalar(0), Scalar(10)); + std::sort(singVals.begin(), singVals.end()); + auto singIter = singVals.rbegin(); + for (int i = 0; i < rank; i++) + { + s.at(i, i) = *singIter++; + } + + if (symmetrical) + return u * s * u.t(); + else + return u * s * v.t(); +} + +Mat buildRandomMat(int rows, int cols, int mtype, RNG& rng, int rank, bool symmetrical) +{ + if (mtype == CV_32F) + { + return buildRandomMat(rows, cols, rng, rank, symmetrical); + } + else if (mtype == CV_64F) + { + return buildRandomMat(rows, cols, rng, rank, symmetrical); + } + else + { + CV_Error(cv::Error::StsBadArg, "This type is not supported"); + } +} + +CV_ENUM(SolveDecompEnum, DECOMP_LU, DECOMP_SVD, DECOMP_EIG, DECOMP_CHOLESKY, DECOMP_QR) + +enum RankMatrixOptions +{ + RANK_HALF, RANK_MINUS_1, RANK_FULL +}; + +CV_ENUM(RankEnum, RANK_HALF, RANK_MINUS_1, RANK_FULL) + +enum SolutionsOptions +{ + NO_SOLUTIONS, ONE_SOLUTION, MANY_SOLUTIONS +}; + +CV_ENUM(SolutionsEnum, NO_SOLUTIONS, ONE_SOLUTION, MANY_SOLUTIONS) + +typedef perf::TestBaseWithParam> SolveTest; + +PERF_TEST_P(SolveTest, randomMat, ::testing::Combine( + ::testing::Values(31, 64, 100), + ::testing::Values(RANK_HALF, RANK_MINUS_1, RANK_FULL), + ::testing::Values(CV_32F, CV_64F), + ::testing::Values(DECOMP_LU, DECOMP_SVD, DECOMP_EIG, DECOMP_CHOLESKY, DECOMP_QR), + ::testing::Bool(), // normal + ::testing::Values(NO_SOLUTIONS, ONE_SOLUTION, MANY_SOLUTIONS) + )) +{ + auto t = GetParam(); + int size = std::get<0>(t); + auto rankEnum = std::get<1>(t); + int mtype = std::get<2>(t); + int method = std::get<3>(t); + bool normal = std::get<4>(t); + auto solutions = std::get<5>(t); + + bool symmetrical = (method == DECOMP_CHOLESKY || method == DECOMP_LU); + + if (normal) + { + method |= DECOMP_NORMAL; + } + + int rank = size; + switch (rankEnum) + { + case RANK_HALF: rank /= 2; break; + case RANK_MINUS_1: rank -= 1; break; + default: break; + } + + RNG& rng = theRNG(); + Mat A = buildRandomMat(size, size, mtype, rng, rank, symmetrical); + Mat x(size, 1, mtype); + Mat b(size, 1, mtype); + + switch (solutions) + { + // no solutions, let's make b random + case NO_SOLUTIONS: + { + rng.fill(b, RNG::UNIFORM, Scalar(-1), Scalar(1)); + } + break; + // exactly 1 solution, let's combine b from A and x + case ONE_SOLUTION: + { + rng.fill(x, RNG::UNIFORM, Scalar(-10), Scalar(10)); + b = A * x; + } + break; + // infinitely many solutions, let's make b zero + default: + { + b = 0; + } + break; + } + + TEST_CYCLE() cv::solve(A, b, x, method); + + SANITY_CHECK_NOTHING(); +} + +typedef perf::TestBaseWithParam, RankEnum, MatDepth, bool, bool>> SvdTest; + +PERF_TEST_P(SvdTest, decompose, ::testing::Combine( + ::testing::Values(std::make_tuple(5, 15), std::make_tuple(32, 32), std::make_tuple(100, 100)), + ::testing::Values(RANK_HALF, RANK_MINUS_1, RANK_FULL), + ::testing::Values(CV_32F, CV_64F), + ::testing::Bool(), // symmetrical + ::testing::Bool() // needUV + )) +{ + auto t = GetParam(); + auto rc = std::get<0>(t); + auto rankEnum = std::get<1>(t); + int mtype = std::get<2>(t); + bool symmetrical = std::get<3>(t); + bool needUV = std::get<4>(t); + + int rows = std::get<0>(rc); + int cols = std::get<1>(rc); + + if (symmetrical) + { + rows = max(rows, cols); + cols = rows; + } + + int rank = std::min(rows, cols); + switch (rankEnum) + { + case RANK_HALF: rank /= 2; break; + case RANK_MINUS_1: rank -= 1; break; + default: break; + } + + int flags = needUV ? 0 : SVD::NO_UV; + + RNG& rng = theRNG(); + Mat A = buildRandomMat(rows, cols, mtype, rng, rank, symmetrical); + TEST_CYCLE() cv::SVD svd(A, flags); + + SANITY_CHECK_NOTHING(); +} + + +PERF_TEST_P(SvdTest, backSubst, ::testing::Combine( + ::testing::Values(std::make_tuple(5, 15), std::make_tuple(32, 32), std::make_tuple(100, 100)), + ::testing::Values(RANK_HALF, RANK_MINUS_1, RANK_FULL), + ::testing::Values(CV_32F, CV_64F), + // back substitution works the same regardless of source matrix properties + ::testing::Values(true), + // back substitution has no sense without u and v + ::testing::Values(true) // needUV + )) +{ + auto t = GetParam(); + auto rc = std::get<0>(t); + auto rankEnum = std::get<1>(t); + int mtype = std::get<2>(t); + + int rows = std::get<0>(rc); + int cols = std::get<1>(rc); + + int rank = std::min(rows, cols); + switch (rankEnum) + { + case RANK_HALF: rank /= 2; break; + case RANK_MINUS_1: rank -= 1; break; + default: break; + } + + RNG& rng = theRNG(); + Mat A = buildRandomMat(rows, cols, mtype, rng, rank, /* symmetrical */ false); + cv::SVD svd(A); + // preallocate to not spend time on it during backSubst() + Mat dst(cols, 1, mtype); + Mat rhs(rows, 1, mtype); + rng.fill(rhs, RNG::UNIFORM, Scalar(-10), Scalar(10)); + + TEST_CYCLE() svd.backSubst(rhs, dst); + + SANITY_CHECK_NOTHING(); +} + + typedef perf::TestBaseWithParam< testing::tuple > KMeans; PERF_TEST_P_(KMeans, single_iter)