| // Benchmarks for the Cauchy operator: the O(mn)-flop, O(m+n)-storage direct |
| // product against its dense equivalents (with and without materializing the |
| // matrix first), and the O(n^2) GKO pivoted solver (CauchyLU) against the |
| // O(n^3) dense PartialPivLU of the materialized matrix. |
| // SPDX-FileCopyrightText: The Eigen Authors |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #include <benchmark/benchmark.h> |
| #include <Eigen/Core> |
| #include <Eigen/LU> |
| #include <contrib/Eigen/StructuredMatrices> |
| |
| using namespace Eigen; |
| |
| typedef Matrix<double, Dynamic, 1> Vec; |
| typedef Matrix<double, Dynamic, Dynamic> Mat; |
| |
| // After reversing its rows, this Cauchy matrix has diagonal 4n and off-diagonal |
| // row sums below 2 H_n. It is well conditioned while still exercising row |
| // pivoting in CauchyLU. |
| static void wellConditionedNodes(Index n, Vec& x, Vec& y) { |
| x.resize(n); |
| y.resize(n); |
| const double delta = 1.0 / (4.0 * static_cast<double>(n)); |
| for (Index i = 0; i < n; ++i) { |
| x[i] = static_cast<double>(n - 1 - i); |
| y[i] = static_cast<double>(i) - delta; |
| } |
| } |
| |
| static Mat denseCauchy(const Vec& x, const Vec& y) { |
| Mat dense(x.size(), y.size()); |
| for (Index j = 0; j < y.size(); ++j) |
| for (Index i = 0; i < x.size(); ++i) dense(i, j) = 1.0 / (x[i] - y[j]); |
| return dense; |
| } |
| |
| static Vec benchmarkVector(Index n) { return Vec::LinSpaced(n, -1.0, 1.0); } |
| |
| static bool productIsAccurate(const Vec& product, const Vec& x, const Vec& y, const Vec& vector) { |
| if (!product.allFinite()) return false; |
| Vec reference = Vec::Zero(x.size()); |
| for (Index i = 0; i < x.size(); ++i) |
| for (Index j = 0; j < y.size(); ++j) reference[i] += vector[j] / (x[i] - y[j]); |
| const double tolerance = 64.0 * static_cast<double>(y.size()) * NumTraits<double>::epsilon(); |
| return (product - reference).norm() <= tolerance * (reference.norm() + 1.0); |
| } |
| |
| static bool solveIsAccurate(const Mat& matrix, const Vec& solution, const Vec& rhs) { |
| if (!solution.allFinite()) return false; |
| const Vec residual = matrix * solution - rhs; |
| if (!residual.allFinite()) return false; |
| const double scale = matrix.norm() * solution.norm() + rhs.norm(); |
| const double tolerance = 512.0 * static_cast<double>(matrix.rows()) * NumTraits<double>::epsilon(); |
| return residual.norm() <= tolerance * scale; |
| } |
| |
| // --- Matrix-vector product w = C * v --- |
| static void BM_CauchyProduct(benchmark::State& state) { |
| const Index n = state.range(0); |
| Vec x, y; |
| wellConditionedNodes(n, x, y); |
| Cauchy<double> C(x, y); // O(n) storage; entries generated on the fly |
| Vec v = benchmarkVector(n), w(n); |
| { |
| const Vec product = C * v; |
| if (!productIsAccurate(product, x, y, v)) { |
| state.SkipWithError("Cauchy product validation failed"); |
| return; |
| } |
| } |
| for (auto _ : state) { |
| w.noalias() = C * v; |
| benchmark::DoNotOptimize(w.data()); |
| benchmark::ClobberMemory(); |
| } |
| } |
| BENCHMARK(BM_CauchyProduct)->Arg(64)->Arg(256)->Arg(1024); |
| |
| static void BM_DenseCauchyProduct(benchmark::State& state) { |
| // The dense product once the matrix has been materialized (O(n^2) storage). |
| const Index n = state.range(0); |
| Vec x, y; |
| wellConditionedNodes(n, x, y); |
| Mat dense = denseCauchy(x, y); |
| Vec v = benchmarkVector(n), w(n); |
| { |
| const Vec product = dense * v; |
| if (!productIsAccurate(product, x, y, v)) { |
| state.SkipWithError("dense product validation failed"); |
| return; |
| } |
| } |
| for (auto _ : state) { |
| w.noalias() = dense * v; |
| benchmark::DoNotOptimize(w.data()); |
| benchmark::ClobberMemory(); |
| } |
| } |
| BENCHMARK(BM_DenseCauchyProduct)->Arg(64)->Arg(256)->Arg(1024); |
| |
| static void BM_DenseCauchyMaterializeProduct(benchmark::State& state) { |
| // The dense equivalent when the matrix is not kept around: materialize from |
| // the node vectors, then multiply. |
| const Index n = state.range(0); |
| Vec x, y; |
| wellConditionedNodes(n, x, y); |
| Cauchy<double> C(x, y); |
| Vec v = benchmarkVector(n), w(n); |
| { |
| const Mat dense = C; |
| const Vec product = dense * v; |
| if (!productIsAccurate(product, x, y, v)) { |
| state.SkipWithError("materialized product validation failed"); |
| return; |
| } |
| } |
| for (auto _ : state) { |
| Mat dense = C; |
| w.noalias() = dense * v; |
| benchmark::DoNotOptimize(w.data()); |
| benchmark::ClobberMemory(); |
| } |
| } |
| BENCHMARK(BM_DenseCauchyMaterializeProduct)->Arg(64)->Arg(256)->Arg(1024); |
| |
| // --- Square solve C * u = b from the node vectors --- |
| static void BM_CauchyLUSolve(benchmark::State& state) { |
| const Index n = state.range(0); |
| Vec x, y; |
| wellConditionedNodes(n, x, y); |
| Cauchy<double> C(x, y); |
| Vec b(n); |
| { |
| const Vec expected = benchmarkVector(n); |
| const Mat dense = C; |
| b = dense * expected; |
| CauchyLU<double> check(C); |
| if (check.info() != Success) { |
| state.SkipWithError("CauchyLU factorization failed"); |
| return; |
| } |
| const Vec solution = check.solve(b); |
| if (!solveIsAccurate(dense, solution, b)) { |
| state.SkipWithError("CauchyLU validation failed"); |
| return; |
| } |
| } |
| Vec u(n); |
| for (auto _ : state) { |
| CauchyLU<double> lu(C); // O(n^2) pivoted factorization (GKO) |
| u = lu.solve(b); |
| benchmark::DoNotOptimize(u.data()); |
| benchmark::ClobberMemory(); |
| } |
| } |
| BENCHMARK(BM_CauchyLUSolve)->Arg(64)->Arg(256)->Arg(1024); |
| |
| static void BM_DensePartialPivLUSolve(benchmark::State& state) { |
| const Index n = state.range(0); |
| Vec x, y; |
| wellConditionedNodes(n, x, y); |
| Mat dense = denseCauchy(x, y); |
| Vec b(n); |
| { |
| const Vec expected = benchmarkVector(n); |
| b = dense * expected; |
| PartialPivLU<Mat> check(dense); |
| const Vec solution = check.solve(b); |
| if (!solveIsAccurate(dense, solution, b)) { |
| state.SkipWithError("PartialPivLU validation failed"); |
| return; |
| } |
| } |
| Vec u(n); |
| for (auto _ : state) { |
| PartialPivLU<Mat> lu(dense); // O(n^3) pivoted factorization |
| u = lu.solve(b); |
| benchmark::DoNotOptimize(u.data()); |
| benchmark::ClobberMemory(); |
| } |
| } |
| BENCHMARK(BM_DensePartialPivLUSolve)->Arg(64)->Arg(256)->Arg(1024); |