blob: 0bcfea92e61741bd23f9e834b7ceb28c4cf3b14f [file]
// 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);